P4302字符串折叠
P4302字符串折叠
区间DP
f[i][j]表示i到j的最小表示
枚举区间[l,r],当循环节的长度是[r-l+1]的因数时可以循环,否则不能
因为在压缩后的字符串中一位数字也算一个字符,所以要分情况计算压缩后的字符串长度:
- 当循环100次时,返回3(位数)+2(括号占2个字符)+f[l][l+k-1](循环节长度)
- 当循环10~99次时,返回2(位数)+2(括号占2个字符)+f[l][l+k-1](循环节长度)
- 当循环0~9次时,返回1(位数)+2(括号占2个字符)+f[l][l+k-1](循环节长度)
以上是折叠之后的情况
但是考虑到折叠后的长度(记为tmp)可能不如原长(f[j][j+i])更优,所以对tmp和f[j][j+i]取min
前面这些都记录好之后显然就有了状态转移方程
for(int k=j;k<i+j;k++){
f[j][j+i]=min(f[j][j+i],f[j][k]+f[k+1][j+i]);
}
就找出来整个字符串的最小长度了
code
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define maxn 110
using namespace std;
int len,tmp;
int f[maxn][maxn];//f[i][j]表示i到j的最小长度 注意,折叠之后可能反而比不折叠的还长
char s[maxn];
int check(int l,int r,int k){
for(int i=0;i<=r-l;i++){//暴力枚举因数,判断循环节的长度是不是满足在区间[l,r]内循环
if(s[l+i]!=s[l+(i%k)]) return 0x7fffffff;
}
if((r-l+1)/k==100) return 3+2+f[l][l+k-1];
if((10<=(r-l+1)/k)&&((r-l+1)/k<100)) return 2+2+f[l][l+k-1];
if((0<=(r-l+1)/k)&&((r-l+1)/k<10)) return 1+2+f[l][l+k-1];
}
int main(){
cin>>s+1;
len=strlen(s+1);
// cout<<len<<endl;//
for(int i=0;i<len;i++){
for(int j=1;i+j<=len;j++){
f[j][j+i]=i+1;
for(int k=1;k<=i+1;k++){//循环节的长度
if((i+1)%k==0){
tmp=check(j,j+i,k);
f[j][j+i]=min(f[j][j+i],tmp);
}
}
for(int k=j;k<i+j;k++){
f[j][j+i]=min(f[j][j+i],f[j][k]+f[k+1][j+i]);//状态转移方程
}
}
}
cout<<f[1][len]<<endl;
return 0;
}
P1941飞扬的小鸟
P1941飞扬的小鸟
一看就是DP,但是有好多疯狂的小细节啊QAQ
本质上是背包。上升是完全背包,因为它可以在单位时间内升好多次并且次数是整数;下降是01背包,因为在单位时间内最多降一次,只有降/不降两种选择
f[i][j]表示到达点(i,j)时最少要用的次数
上升的前一状态有两种,下降的前一状态也有两种,具体的见代码
统计答案的时候分开讨论:
先扫一遍最后一列上的点,如果有比0x3f3f3f3f小的数那太好了可以完成游戏,直接输出最小值就好了
要是没有就说明游戏失败了,那就倒着往前扫,看前面什么时候遇到符合条件的,OK记住这个点,再扫一遍所有的管道,看有多少个管道在这个点前面,得到答案
难道这就结束了吗?显然没有
疯狂的小细节:
- 超出m,小鸟就不能再上升了,这时要判一下,在f[i][m]和f[i][j]之间取min(相当于鸟撞天花板上了直接压在这个高度了)
- 不是管道的地方都是墙过不去,给这样的点赋个极大值就好了
- 因为数据范围不大,所以把管道的最高点和最低点用up[]和down[]存一下,把它们转化成这个网格图上的点后面就很好用
一开始没想到就觉得很难写 - 一点奇奇怪怪的东西:memset是按位赋值,二维数组每一位赋了0x3f之后f[i][j]就成了0x3f3f3f3f(别问我为啥会纠结在这个上qwq
code
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define inf 1061109567
#define maxn 10010
#define maxm 2010
using namespace std;
template<typename T>
inline void read(T &x){
x=0; bool flag=0; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
for(;isdigit(c);c=getchar()) x=x*10+(c^48);
if(flag) x=-x;
}
int n,m,k,x[maxn],y[maxn];
int ans,tmp1,tmp2,cnt;
int f[maxn][maxm];//f[i][j] -> [i,j]
int up[maxn],down[maxn];
bool flag[maxn];
struct Pipe{
int p;
int l;
int h;
}pip[maxn];
//bool cmp(Pipe a,Pipe b){
// return a.p<b.p;
//}
void dp(){
for(int i=1;i<=n;i++){
for(int j=1+x[i];j<=m+x[i];j++){//升
f[i][j]=min(f[i][j-x[i]],f[i-1][j-x[i]])+1;
}
for(int j=m+1;j<=m+x[i];j++){//判最大值
f[i][m]=min(f[i][m],f[i][j]);
}
for(int j=1;j<=m-y[i];j++){//降
f[i][j]=min(f[i][j],f[i-1][j+y[i]]);
}
// for(int j=1;j<=pip[i].l;j++) f[i][j]=0x3f;
// for(int j=pip[i].h;j<=m;j++) f[i][j]=0x3f;
for(int j=1;j<=down[i]-1;j++) f[i][j]=0x3f3f3f3f;
for(int j=up[i]+1;j<=m;j++) f[i][j]=0x3f3f3f3f;
}
// cout<<"*"<<endl;
// for(int i=1;i<=n;i++){
// for(int j=1;j<=m;j++)
// cout<<f[i][j]<<" ";
// cout<<endl;
// }
for(int j=1;j<=m;j++) ans=min(ans,f[n][j]);
// for(int j=1;j<=m;j++) cout<<"**"<<f[n][j]<<endl;
if(ans>=0x3f3f3f3f) {
cout<<0<<endl;
for(int i=n;i>=1;i--){//倒着搜找走的最长的
for(int j=1;j<=m;j++){
tmp1=i;tmp2=j;
if(f[i][j]<0x3f3f3f3f){
break;
}
}
if(tmp2<m) break;
}
// cout<<"tmp1="<<tmp1<<endl;
// cout<<"tmp2="<<tmp2<<endl;
for(int i=1;i<=k;i++){
if(pip[i].p<=tmp1) cnt++;//注意是小于等于,不是小于,因为等于的时候也符合,能算进去一个QAQ
}
cout<<cnt<<endl;
}
else {
cout<<1<<endl;
cout<<ans<<endl;
}
}
int main(){
// freopen("P1941_8.in","r",stdin);
read(n),read(m),read(k);
for(int i=1;i<=n;i++) up[i]=m,down[i]=1;
memset(f,0x3f,sizeof(f));
for(int i=1;i<=m;i++) f[0][i]=0;
for(int i=1;i<=n;i++) read(x[i]),read(y[i]);
for(int i=1;i<=k;i++) {
read(pip[i].p),read(pip[i].l),read(pip[i].h);
flag[pip[i].p]=1;
up[pip[i].p]=pip[i].h-1;//管道最高点
down[pip[i].p]=pip[i].l+1;//管道最低点
}
ans=0x3f3f3f3f;
// cout<<"***"<<ans<<endl;
// sort(pip+1,pip+k+1,cmp);
dp();
// cout<<"***"<<f[0][0]<<endl;
return 0;
}
[NOIP2015四校联训day6]小奇的仓库
换根DP+位运算拆位
一道很好的树形DP可惜我不会
感谢巨佬wsy_jim 熹圜 Whisper_Rain
本质就是:刨去自己的子树,然后把外面的加进来的过程——Whisper_Rain
解释放在代码里了虽然也不知道有没有说清楚
code
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#define ll long long
#define maxn 100010
using namespace std;
template<typename T>
inline void read(T &x){
x=0; bool flag=0; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
for(;isdigit(c);c=getchar()) x=x*10+(c^48);
if(flag) x=-x;
}
ll n,m,a,b,c,k,ans;
ll g[maxn],other[16],f[maxn][16],h[maxn];//g应该开到maxn而不是16...惨痛教训
struct node{
int to;
int nxt;
int v;
}e[2*maxn];
void add(int u,int v,int x){
e[++k].to=v;//点的编号从1开始,所以不用把h初始化为-1
e[k].nxt=h[u];
e[k].v=x;
h[u]=k;
}
void dfs1(int x,int fa){//计算这个节点到它的子树中所有点的距离之和
for(int i=h[x];i;i=e[i].nxt){
ll t=e[i].to;
if(t!=fa){
dfs1(t,x);
f[x][e[i].v%16]++;//记录i为起点%16==j的边的个数
g[x]+=g[t]+e[i].v/16;//因为xor在0~15内,影响不到>=16的,所以把它们放到一起
for(int j=0;j<16;j++){//在子树里找点更新
ll k=j+e[i].v;//********
f[x][k%16]+=f[t][j];
g[x]+=k/16*f[t][j];//重新算边权
}
}
}
}
void dfs2(int x,int fa){//计算这个点到它的子树外的所有点的距离之和
for(int i=h[x];i;i=e[i].nxt){
ll t=e[i].to;
if(t!=fa){
ll tmp=g[x]-g[t];
for(int j=0;j<16;j++){
ll k=j+e[i].v;
tmp-=k/16*f[t][j];
other[k%16]=f[x][k%16]-f[t][j];//other[]存这个点除了它的子树之外%16==j的点的个数
}
other[e[i].v%16]--;
f[t][e[i].v%16]++;
g[t]+=tmp;
for(int j=0;j<16;j++){
ll k=j+e[i].v;//********
f[t][k%16]+=other[j];
g[t]+=k/16*other[j];
}
dfs2(t,x);
}
}
}
int main(){
read(n),read(m);
for(int i=1;i<=n-1;i++){
read(a),read(b),read(c);
add(a,b,c);
add(b,a,c);
}
// memset(h,-1,sizeof(h));
dfs1(1,0);
dfs2(1,0);
// for(int i=1;i<=n;i++){//
// for(int j=0;j<16;j++)//
// cout<<" *"<<f[i][j];//
// cout<<endl;//
// }//
for(int i=1;i<=n;i++){
ans=16*g[i];
for(int j=0;j<16;j++) ans+=(j^m)*f[i][j];
printf("%lld
",ans);
}
return 0;
}