动态规划复习
发现一个整理dp基础方程整理得挺好的博客:http://www.cnblogs.com/keshuqi/p/7715167.html
发现一个总结多叉树树形背包常见建模方法的博客:http://blog.csdn.net/no1_terminator/article/details/77824790
最近除了模拟赛和往年noip题自我测试,就只能搞点弱项专题训练了。
都是洛谷上的题,每次从水题开始:
区间dp,对于一个字母,增删其实效果是相同的,取代价最小的即可。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=3000+10; int n,m,val[maxn],dp[maxn][maxn]; char c,s[maxn]; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<'0'||cc>'9') cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); return aa; } int main() { m=read();n=read(); int x,y; scanf("%s",s+1); for(int i=1;i<=m;++i) { do c=getchar();while(c<'a'||c>'z'); x=c-'a'+1; y=min(read(),read()); val[x]=y; } memset(dp,0x3f3f3f3f,sizeof(dp)); for(int i=1;i<=n;++i) dp[i][i]=0; for(int i=1;i<n;++i) if(s[i]==s[i+1]) dp[i][i+1]=0; for(int l=1;l<n;++l) { for(int i=1;i<=n-l;++i) { int j=i+l; if(s[i]==s[j]&&l>1) dp[i][j]=min(dp[i][j],dp[i+1][j-1]); dp[i][j]=min(dp[i][j],dp[i+1][j]+val[s[i]-'a'+1]); dp[i][j]=min(dp[i][j],dp[i][j-1]+val[s[j]-'a'+1]); } } printf("%d",dp[1][n]); return 0; }
道路游戏:
这是一道其实只有普及+/提高-的题,因为三方可过。
三方的代码:
int get_sum(int r,int pos,int t) {
pos=(pos-r-1+n*m)%n+1;
return sum[pos][r+t]-sum[pos][r];
}int main() {
n=read();m=read();p=read();
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) tu[i][j]=read();
for(int i=1;i<=n;++i) cost[i]=read();
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) sum[i][j]=sum[i][j-1]+tu[(i+j-2)%n+1][j];
for(int i=1;i<=m;++i) dp[i]=-INF;
for(int i=0;i<m;++i) for(int j=1;j<=n;++j)
for(int k=1;k<=p&&k+i<=m;++k) dp[i+k]=max(dp[i+k],dp[i]+get_sum(i,j,k)-cost[j]);
printf("%d",dp[m]);
return 0;
}
肯定是平方的解决方式要好一点啊,此题用三方做有什么意义呢?但是我太菜不会平方啊。。。
奶牛浴场:
这道题似乎哪里见过。似乎是做过的?
一看那个n是可以平方的,l、w是不行的,那肯定是n平方的喽。
情况比较多,需要都考虑到,但是数据比较水我也没有办法。
借用洛谷上 I_AM_HelloWord 的题解(侵删):
先枚举极大子矩形的左边界,然后从左到右依次扫描每一个障碍点,并不断修改可行的上下边界,从而枚举出所有以这个定点为左边界的极大子矩形。考虑如图2中的三个点,现在我们要确定所有以1号点为左边界的极大矩形。先将1号点右边的点按横坐标排序。然后按从左到右的顺序依次扫描1号点右边的点,同时记录下当前的可行的上下边界。
开始时令当前的上下边界分别为整个矩形的上下边界。然后开始扫描。第一次遇到2号点,以2号点作为右边界,结合当前的上下边界,就得到一个极大子矩形(如图3)。
同时,由于所求矩形不能包含2号点,且2号点在1号点的下方,所以需要修改当前的下边界,即以2号点的纵坐标作为新的下边界。第二次遇到3号点,这时以3号点的横坐标作为右边界又可以得到一个满足性质1的矩形(如图4)。
类似的,需要相应地修改上边界。以此类推,如果这个点是在当前点(确定左边界的点)上方,则修改上边界;如果在下方,则修改下边界;如果处在同一行,则可中止搜索(因为后面的矩形面积都是0了)。由于已经在障碍点集合中增加了整个矩形右上角和右下角的两个点,所以不会遗漏右边界与整个矩形的右边重合的极大子矩形(如图5)。
需要注意的是,如果扫描到的点不在当前的上下边界内,那么就不需要对这个点进行处理。
这样做是否将所有的极大子矩形都枚举过了呢?
可以发现,这样做只考虑到了左边界覆盖一个点的矩形,因此我们还需要枚举左边界与整个矩形的左边界重合的情况。这还可以分为两类情况。一种是左边界与整个举行的左边界重合,而右边界覆盖了一个障碍点的情况,对于这种情况,可以用类似的方法从右到左扫描每一个点作为右边界的情况。
另一种是左右边界均与整个矩形的左右边界重合的情况,对于这类情况我们可以在预处理中完成:先将所有点按纵坐标排序,然后可以得到以相邻两个点的纵坐标为上下边界,左右边界与整个矩形的左右边界重合的矩形,显然这样的矩形也是极大子矩形,因此也需要被枚举到。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=5000+10; int l,w,n,ans; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<'0'||cc>'9') cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); return aa; } struct Node{ int x,y; }node[maxn]; bool cmp(const Node& a,const Node& b) { return a.x < b.x; } bool cmp2(const Node& a,const Node& b) { return a.y < b.y; } int main() { l=read();w=read();n=read(); for(int i=1;i<=n;++i) { node[i].x=read(); node[i].y=read(); } node[++n].x=0;node[n].y=0; node[++n].x=l;node[n].y=0; node[++n].x=0;node[n].y=w; node[++n].x=l;node[n].y=w; sort(node+1,node+n+1,cmp); int xx,yy; for(int i=1;i<=n;++i) { xx=0;yy=w; if(i!=1) ans=max(ans,(node[i].x-node[i-1].x)*w); for(int j=i+1;j<=n;++j) { ans=max(ans,(node[j].x-node[i].x)*(yy-xx)); if(node[j].y<=xx||node[j].y>=yy) continue; if(node[j].y<node[i].y) xx=node[j].y; else if(node[j].y>node[i].y) yy=node[j].y; else break; } xx=0;yy=w; for(int j=i-1;j;--j) { ans=max(ans,(node[i].x-node[j].x*(yy-xx))); if(node[j].y<=xx||node[j].y>=yy) continue; if(node[j].y<node[i].y) xx=node[j].y; else if(node[j].y>node[i].y) yy=node[j].y; else break; } } sort(node+1,node+n+1,cmp2); for(int i=1;i<n;++i) ans=max(ans,(node[i+1].y-node[i].y)*l); printf("%d",ans); return 0; }
翻转棋:
为什么感觉都做过呢,套路啊。。。
我们知道,一个格子要么翻一次要么不翻,我们发现如果第一行的格子翻或不翻的状态定了,那么所有格子的状态就定了。
由于n、m只有15,我们就直接枚举第一行格子的状态然后直接算出后面格子的状态。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=15+2,maxs=1<<15|2,INF=1e7; int n,m,now,nowans,ans=INF,ff[maxn]; bool ok,ykk; bool tu[maxn][maxn],num[maxn][maxn]; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<'0'||cc>'9') cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); return aa; } void get_num(int p) { nowans=0; for(int i=m;i;--i) { nowans+=(num[1][i]=(p&1)); p>>=1; } } int get_now(int x,int y) { int rs=tu[x][y]^num[x][y]; if(x>1) rs^=num[x-1][y]; if(x<n) rs^=num[x+1][y]; if(y>1) rs^=num[x][y-1]; if(y<m) rs^=num[x][y+1]; return rs; } void print_ans() { memset(ff,0,sizeof(ff)); for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(num[i][j]) ff[i]|=(1<<j-1); ans=nowans; } int main() { n=read(); m=read(); for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) tu[i][j]=read(); for(int p=0;p<(1<<m);++p) { memset(num,0,sizeof(num)); get_num(p); ok=0; for(int i=2;i<=n&&nowans<ans;++i) for(int j=1;j<=m;++j) { now=get_now(i-1,j); if(now) nowans+=(num[i][j]=1); } for(int i=1;i<=m;++i) if(get_now(n,i)) { ok=1; break; } if(!ok&&nowans<ans) print_ans(); } if(ans==INF) printf("IMPOSSIBLE\n"); else { for(int i=1;i<=n;++i) { for(int j=1;j<=m;++j) { printf("%d ",ff[i]&1); ff[i]>>=1; } printf("\n"); } } return 0; }
多人背包:
求前k优解的和。
刚开始看到这个题的时候觉得非常不可做。
然后就想了一会(反正脑子也不清醒也想不到什么)就看题解了,然后题解说归并排序,秒懂。
假如dp[i][j][t]表示目前取到第i件物品,消耗体积j,的第t优解。
那么它一定由dp[i-1][j]、dp[i-1][j-v[i]]转移过来,直接把这两个里面前k个最优的归并弄出来就可以了。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxv=5000+10,maxk=50+5,maxn=200+10,INF=1e8; int k,v,n,f[maxv][maxk],g[maxv][maxk],p[maxn],w[maxn],ans; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<'0'||cc>'9') cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); return aa; } int main() { k=read();v=read();n=read(); for(int i=1;i<=n;++i) { p[i]=read();w[i]=read(); } for(int i=0;i<=v;++i) for(int t=1;t<=k;++t) g[i][t]=-INF; g[0][1]=0; for(int i=1;i<=n;++i) { for(int j=0;j<=v;++j) { if(j<p[i]) for(int t=1;t<=k;++t) f[j][t]=g[j][t]; else { int pos1=1,pos2=1; for(int t=1;t<=k;++t) { if(pos2>k||(pos1<=k&&g[j][pos1]>=g[j-p[i]][pos2]+w[i])) f[j][t]=g[j][pos1++]; else f[j][t]=g[j-p[i]][pos2++]+w[i]; } } } memcpy(g,f,sizeof(f)); memset(f,0,sizeof(f)); } for(int i=1;i<=k;++i) ans+=g[v][i]; printf("%d",ans); return 0; }
一道树dp裸题,但是一直没有过,弄了很久发现是一个地方z写成了y。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=100+10,INF=1e6; int n,m,dp[maxn][maxn],sum; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<'0'||cc>'9') cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); return aa; } int fir[maxn],to[2*maxn],nxt[2*maxn],e=0,v[2*maxn]; void add(int x,int y,int z) { to[++e]=y;nxt[e]=fir[x];fir[x]=e;v[e]=z; to[++e]=x;nxt[e]=fir[y];fir[y]=e;v[e]=z; } int g[maxn],size[maxn],tot[maxn]; void dfs(int pos,int f) { int y,z; size[pos]=1; dp[pos][0]=0; for(y=fir[pos];y;y=nxt[y]) { if(f==(z=to[y])) continue; g[z]=v[y]; dfs(z,pos); size[pos]+=size[z]; g[pos]+=g[z]; for(int i=m;~i;--i) { for(int j=0;j<=min(i,size[z]-1);++j) dp[pos][i]=min(dp[pos][i],dp[z][j]+dp[pos][i-j]); if(i>=size[z]) dp[pos][i]=min(dp[pos][i],dp[pos][i-size[z]]+g[z]); } } } int main() { n=read();m=n-1-read(); int x,y,z; memset(dp,0x3f3f3f3f,sizeof(dp)); for(int i=1;i<n;++i) { x=read();y=read();z=read(); sum+=z; add(x,y,z); } dfs(1,0); printf("%d",sum-dp[1][m]); return 0; }
这道题让我想起了一道题,但是记不到那道题具体是什么了,只记得当时没有看懂题目,主要是没有理解两个人都足够聪明是什么意思。
感觉这道题这句话是一样的道理:两个玩家都希望拿到最多钱数的硬币。
希望是希望,不一定能成功,但是题目的意思就是两个人都是最优策略。
A希望取价值和最大,相当于他想让B取价值和最小,但是在A取后B一定会使用最优策略,所以A要使B最优策略最劣。
然后就只能倒着做了。dp[i][j]表示取第i个的人在这一次取j个硬币可以得到的最大价值,为了简化转移,我们dp表示一个前缀最大价值来搞:
dp[i][j]=max(dp[i][j-1],w[i]-dp[i+j][min(n-i-j+1,2*j)]);
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=2000+10; int n,w[maxn],dp[maxn][maxn]; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<'0'||cc>'9') cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); return aa; } int main() { n=read(); for(int i=1;i<=n;++i) w[i]=read(); for(int i=n;i;--i) w[i]+=w[i+1]; for(int i=n;i;--i) for(int j=1;j<=n-i+1;++j) dp[i][j]=max(dp[i][j-1],w[i]-dp[i+j][min(n-i-j+1,2*j)]); printf("%d",dp[1][2]); return 0; }
水题一道,先把每一段可以匹配的最大值预处理出来,然后dp,注意细节问题。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=600+10; int n,m,len[maxn],mch[maxn][maxn],dp[maxn],debug; char s[maxn],c[maxn][30]; int main() { scanf("%d%d",&n,&m); scanf("%s",s+1); for(int i=1;i<=n;++i) { scanf("%s",c[i]+1); len[i]=strlen(c[i]+1); } int x,y; for(int i=1;i<=m;++i) { for(int j=1;j<=n;++j) { if(len[j]>m-i+1) continue; x=i;y=1; while(y<=len[j]&&x<=m) { while(x<=m&&s[x]!=c[j][y]) x++; if(x<=m&&s[x]==c[j][y]) y++,x++; } if(y>len[j]) mch[i][x-1]=max(mch[i][x-1],len[j]); } } for(int l=1;l<m;++l) for(int i=1;i<=m-l;++i) { mch[i][i+l]=max(mch[i][i+l],mch[i][i+l-1]); mch[i][i+l]=max(mch[i][i+l],mch[i+1][i+l]); } for(int i=1;i<=m;++i) { dp[i]=max(dp[i],dp[i-1]); for(int j=0;j<i;++j) dp[i]=max(dp[i],dp[j]+mch[j+1][i]); } printf("%d",m-dp[m]); return 0; }
这个题很有趣啊,感觉题目描述简直有毒,看懂题了之后dp比较裸,不要管我恶意压缩代码。使手中保持10个成品让dp非常简单暴力。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=100+10,maxt=13,maxs=13*13*13; int n,dp[maxn][maxs],sum[maxn][4],mi[5]; char cc; void get_dp(int f,int i,int x,int y,int z) { int t=x+y*mi[1]+z*mi[2]; dp[i][t]=min(dp[i][t],f+1); } int main() { scanf("%d",&n); int x,y,z,now; for(int i=1;i<=n;++i) { do cc=getchar();while(cc<'A'||cc>'C'); x=cc-'A'; for(int p=0;p<3;++p) sum[i][p]=sum[i-1][p]; sum[i][x]++; } if(n<=10) { printf("%d",(sum[n][0]!=0)+(sum[n][1]!=0)+(sum[n][2]!=0)); return 0; } mi[0]=1; for(int i=1;i<=3;++i) mi[i]=mi[i-1]*11; memset(dp,0x3f3f3f3f,sizeof(dp)); x=0; for(int i=0;i<3;++i) x+=mi[i]*sum[10][i]; dp[10][x]=0; for(int i=10;i<=n;++i) for(int j=0;j<mi[3];++j) if(dp[i][j]<=n){ x=j%mi[1]; y=j/mi[1]%mi[1]; z=j/mi[2]; if(i==n) { now=(x!=0)+(y!=0)+(z!=0); dp[n][0]=min(dp[n][0],dp[n][j]+now); continue; } if(x) now=min(n,i+x),get_dp(dp[i][j],now,sum[now][0]-sum[i][0],y+sum[now][1]-sum[i][1],z+sum[now][2]-sum[i][2]); if(y) now=min(n,i+y),get_dp(dp[i][j],now,x+sum[now][0]-sum[i][0],sum[now][1]-sum[i][1],z+sum[now][2]-sum[i][2]); if(z) now=min(n,i+z),get_dp(dp[i][j],now,x+sum[now][0]-sum[i][0],y+sum[now][1]-sum[i][1],sum[now][2]-sum[i][2]); } printf("%d",dp[n][0]); return 0; }
又是一个裸的树dp。不知道大家是怎么选难度等级的。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long const int maxn=1e6+10; int n; ll ans,nowans=1; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<'0'||cc>'9') cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); return aa; } int fir[maxn],to[2*maxn],nxt[2*maxn],e=0; void add(int x,int y) { to[++e]=y;nxt[e]=fir[x];fir[x]=e; to[++e]=x;nxt[e]=fir[y];fir[y]=e; } ll dep[maxn],size[maxn],fa[maxn]; void dfs(int pos,int d) { dep[pos]=d; size[pos]=1; ans+=d; int y,z; for(y=fir[pos];y;y=nxt[y]) { if((z=to[y])==fa[pos]) continue; fa[z]=pos; dfs(z,d+1); size[pos]+=size[z]; } } void dfs2(int pos,ll now,ll tot) { int y,z; if(now>ans||(now==ans&&nowans>pos)) ans=now,nowans=pos; for(y=fir[pos];y;y=nxt[y]) { if((z=to[y])==fa[pos]) continue; dfs2(z,now+tot+size[pos]-2*size[z],tot+size[pos]-size[z]); } } int main() { n=read(); int x,y; for(int i=1;i<n;++i) { x=read(); y=read(); add(x,y); } dfs(1,1); dfs2(1,ans,0); printf("%lld",nowans); return 0; }
打鼹鼠:
还是不懂这道题哪里算得上提高+/省选-难度了。
首先我们不可能直接在n*n的网格图上跑,只能用鼹鼠来dp。
然后考了一个小优化就是当两个鼹鼠的time差>=2*n-2的时候无论怎样都是可以转移的。把鼹鼠按照time排序后就直接维护一个之前的鼹鼠与现在鼹鼠time差>=2*n-2的所有鼹鼠的dp最大值。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=1e4+10; int l,n,dp[maxn],time[maxn],x[maxn],y[maxn],ans,now; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<'0'||cc>'9') cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); return aa; } int main() { l=read(); n=read(); int pos=1; for(int i=1;i<=n;++i) { time[i]=read(); x[i]=read(); y[i]=read(); while(pos<i&&time[i]-time[pos]>=2*n-2) now=max(now,dp[pos++]); dp[i]=max(dp[i],now); for(int j=i-1;j>=pos;--j) if(abs(x[i]-x[j])+abs(y[i]-y[j])<=time[i]-time[j]) dp[i]=max(dp[i],dp[j]); dp[i]++; ans=max(ans,dp[i]); } printf("%d",ans); return 0; }
百日旅行:
又不小心开了一道水题,可以直接贪心水过。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long const int maxn=2e5+10; const ll INF=1e17; ll n,p,q,ans=INF; ll trav(ll day,ll times) { ll x=day/times,y=day%times; return p*(times-y)*x*x+p*y*(x+1)*(x+1); } int main() { scanf("%lld%lld%lld",&n,&p,&q); for(int i=0;i<=n;++i) ans=min(ans,i*q+trav(n-i,i+1)); printf("%lld",ans); return 0; }
跳舞:
我觉得我不能再刷水题了。。。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=5000+10,INF=1e8; int n,T,s[maxn],b[maxn],dp[maxn][maxn],ans; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<'0'||cc>'9') cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); return aa; } int main() { n=read(); T=read(); for(int i=1;i<=n;++i) s[i]=read(); for(int i=1;i<=n;++i) b[i]=read(); for(int i=0;i<=n;++i) for(int j=0;j<T;++j) dp[i][j]=-INF; dp[0][0]=0; int x; for(int i=0;i<n;++i) for(int j=0;j<T;++j) if(dp[i][j]>-INF) { x=(j+1)%T; dp[i+1][j]=max(dp[i+1][j],dp[i][j]-s[i+1]); if(x) dp[i+1][x]=max(dp[i+1][x],dp[i][j]+s[i+1]); else dp[i+1][x]=max(dp[i+1][x],dp[i][j]+s[i+1]+b[i+1]); } for(int i=0;i<T;++i) ans=max(ans,dp[n][i]); printf("%d",ans); return 0; }
豪华游轮:
贪心,考虑把向前走的和向后走的分别合并,然后让中间角度尽可能接近180,剩下角度最后转。
忘记了什么是余弦定理,忘记了角度和弧度怎么转换,特别难过。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define db double const int maxn=50,maxh=1e6+10; const db pi=acos(-1); int n,t; db dis1,dis2,rnd[maxn],ans; char s[22]; bool vis[400],g[400]; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<'0'||cc>'9') cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); return aa; } int main() { n=read(); for(int i=1;i<=n;++i) { scanf("%s",s); if(s[0]=='r') rnd[++t]=read()%360; else if(s[0]=='l') rnd[++t]=(720-read())%360; else if(s[0]=='f') dis1+=read(); else if(s[0]=='b') dis2+=read(); } vis[0]=1; for(int i=1;i<=t;++i) { memcpy(g,vis,sizeof(g)); for(int j=0;j<360;++j) if(g[j]) vis[(int)(j+rnd[i])%360]=1; } for(int i=0;i<=180;++i) if(vis[180+i]||vis[180-i]) { ans=sqrt(dis1*dis1+dis2*dis2-2*dis1*dis2*cos((double)(180-i)*pi/180)); break; } printf("%.6lf",ans); return 0; }
前缀单词:
我说我在后面开几道不是水题的题做,然后就看到这道通过三十多的题,我觉得应该是那种有点难度但是不至于太难的题,但是没想到还是水题,树dp水题,直接建字典树。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll unsigned long long const int maxn=50+5,maxs=maxn*maxn; int n,son[maxs][30],p[maxs],t; char s[maxn]; ll dp[maxn]; void add_s(int pos) { int len=strlen(s+1),now=0,x; for(int i=1;i<=len;++i) { x=s[i]-'a'; if(!son[now][x]) son[now][x]=++t; now=son[now][x]; } p[now]=pos; } int fir[maxs],to[maxs],nxt[maxs],e=0; void add(int x,int y) { to[++e]=y;nxt[e]=fir[x];fir[x]=e; } void dfs1(int pos,int now) { if(p[pos]) add(now,++t),now=t; for(int i=0;i<26;++i) if(son[pos][i]) dfs1(son[pos][i],now); } void dfs2(int pos) { dp[pos]=1; int y,z; for(y=fir[pos];y;y=nxt[y]) { dfs2(z=to[y]); dp[pos]*=dp[z]; } dp[pos]++; } int main() { scanf("%d",&n); for(int i=1;i<=n;++i) scanf("%s",s+1),add_s(i); t=0;dfs1(0,0); dfs2(0); printf("%lld",dp[0]-1); return 0; }
NOI还有水题的啊。。。。
直接算每一位如果是0最后会是什么,如果是1最后会是什么,然后直接数位dp就可以了。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=1e5+10,maxs=35; int n,m,now[2][maxs],dp[maxs],ans; char c[10]; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<'0'||cc>'9') cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); return aa; } void get_now(int &f,int p,int x) { if(p==1) f=f&x; else if(p==2) f=f|x; else f=f^x; } int main() { n=read(); m=read(); int x,y; for(int i=0;i<=32;++i) now[0][i]=0,now[1][i]=1; for(int i=1;i<=n;++i) { scanf("%s",c);y=read(); x= c[0]=='A'? 1 : (c[0]=='O'? 2:3); for(int j=0;j<=32;++j) { get_now(now[0][j],x,y&1); get_now(now[1][j],x,y&1); y>>=1; } } for(int i=0;i<=32;++i) { dp[i]=dp[i-1]; dp[i]|=max(now[0][i],now[1][i])<<i; } x=0; for(int i=32;~i;--i) { if((y=((m>>i)&1))) ans=max(ans,(x|((now[0][i])<<i))|dp[i-1]); x|=((now[y][i])<<i); } ans=max(ans,x); printf("%d",ans); return 0; }
子串:
不是很难,用f[i][j][t][r]表示当前我们A串匹配到了i位置,B串匹配到了j位置,并且一共取了t个子串,目前是否为最后取的子串的最后一位的状态。
第一维滚动,直接dp。注意细节问题。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long const int maxn=1000+10,maxm=200+10; const ll mod=1e9+7; int n,m,k; char a[maxn],b[maxn]; ll f[2][maxm][maxm][2]; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<'0'||cc>'9') cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); return aa; } int main() { n=read();m=read();k=read(); scanf("%s%s",a+1,b+1); int cur=0; f[0][0][1][1]=1; for(int i=1;i<=n;++i) { cur^=1; memset(f[cur],0,sizeof(f[cur])); for(int j=1;j<=m;++j) for(int t=1;t<=min(j,k);++t) { if(a[i]==b[j]) (f[cur][j][t][1]+=f[cur^1][j-1][t][1]+f[cur^1][j-1][t-1][0]+f[cur^1][j-1][t-1][1])%=mod; (f[cur][j][t][0]+=f[cur^1][j][t][0]+f[cur^1][j][t][1])%=mod; } f[cur][0][1][1]=1; } printf("%lld",(f[cur][m][k][0]+f[cur][m][k][1])%mod); return 0; }
树:
这道题说简单吧,但是要写高精,说难吧,其实dp方程和转移也没多难。
用 $ dp[pos][i] $ 表示以 $ pos $ 为根的子树中, $ pos $ 所在连通块大小为i的最大答案(不乘i)。
然后$ dp[pos][0]= \max ( dp[pos][i] \times i ) $ 。
kczno1题解中说复杂度看起来像三方的实际上是平方的:
每次转移是复杂度是 x之前的子树的sz*当前子树的sz
相当于之前子树所有点和当前子树的点组成的点对数
而每个点对只会在lca处被计算一次
所以复杂度O(n^2)
这样转移好处是可以没有除法。
我一开始没写高精交上去发现WA了一堆,我把long long 改成 unsigned long long 发现答案变了。
然后就检查转移方程检查了很久,最后弃疗去看题解,发现竟然和题解的思路一模一样,除了他写了高精。
不想打高精了,放个半成品在这。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll unsigned long long const int maxn=700+10; int n; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<'0'||cc>'9') cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); return aa; } int fir[maxn],to[2*maxn],nxt[2*maxn],e=0; void add(int x,int y) { to[++e]=y;nxt[e]=fir[x];fir[x]=e; to[++e]=x;nxt[e]=fir[y];fir[y]=e; } ll size[maxn],dp[maxn][maxn]; void dfs(int pos,int f) { int y,z;size[pos]=1; for(y=fir[pos];y;y=nxt[y]) { if((z=to[y])==f) continue; dfs(z,pos);size[pos]+=size[z]; } dp[pos][1]=1; ll t; for(y=fir[pos];y;y=nxt[y]) { if((z=to[y])==f) continue; for(int i=size[pos];i;--i) { t=dp[pos][i]*dp[z][0]; for(int j=min(i-1,(int)size[z]);j;--j) dp[pos][i]=max(dp[pos][i],dp[pos][i-j]*dp[z][j]); dp[pos][i]=max(dp[pos][i],t); } } for(int i=1;i<=size[pos];++i) dp[pos][0]=max(dp[pos][0],dp[pos][i]*i); } int main() { n=read(); int x,y; for(int i=1;i<n;++i) { x=read(); y=read(); add(x,y); } dfs(1,0); cout<<dp[1][0]; return 0; }