动态规划:区间DP、状压DP
区间DP:区间dp就是在区间上进行动态规划,求解一段区间上的最优解。主要是通过合并小区间的最优解进而得出整个大区间上最优解的dp算法。例题洛谷P1880、P2858、P1063,POJ1390、POJ2955、POJ3042、POJ1141(POJ的这几道比较难...
将大区间分割为小区间,dp[i][j]=dp[i][k]+dp[k+1][j]。但是考虑到状态的可到达性,直接对i、j循环遍历是不行的。这时候引入l作为区间长度,循环遍历l、i,j可以被表示为i+l
对于是一个环的区间来说,可以将原来单个的区间复制一遍形成双倍的区间,在这个大区间上dp即可。大区间的话,限制条件就要发生改变:i+l<2*n&&i<2*n
//P1880 #include<bits/stdc++.h> using namespace std; int n; int num[300],sum[300],f1[300][300],f2[300][300]; int d(int i,int j) { return sum[j]-sum[i-1]; } int main() { scanf("%d",&n); for(int i=1;i<=n+n;i++){ scanf("%d",&num[i]); num[i+n]=num[i]; sum[i]=sum[i-1]+num[i]; } for(int p=1;p<n;p++){ for(int i=1,j=i+p;(i<n+n)&&(j<n+n);i++,j=i+p){ f2[i][j]=0x7fffffff; for(int k=i;k<j;k++){ f1[i][j]=max(f1[i][j],f1[i][k]+f1[k+1][j]+d(i,j)); f2[i][j]=min(f2[i][j],f2[i][k]+f2[k+1][j]+d(i,j)); printf("%d\n",f2[i][j]); } } } int ans1=0,ans2=0x7fffffff; for(int i=1;i<=n;i++){ ans1=max(f1[i][i+n-1],ans1); ans2=min(f2[i][i+n-1],ans2); } printf("%d\n%d\n",ans2,ans1); return 0; }
//POJ1390 #include<stdio.h> #include<string.h> #include<algorithm> using namespace std; int dp[205][205][205],num[205],rec[205],n; int solve(int l,int r,int k){ if(dp[l][r][k]) return dp[l][r][k]; if(l==r) return (num[r]+k)*(num[r]+k); dp[l][r][k]=solve(l,r-1,0)+(num[r]+k)*(num[r]+k); for(int i=l;i<r;i++){ if(rec[i]==rec[r]){ dp[l][r][k]=max(dp[l][r][k],solve(l,i,num[r]+k)+solve(i+1,r-1,0)); } } return dp[l][r][k]; } int main(){ int t,ct=0; scanf("%d",&t); while(t--){ scanf("%d",&n); memset(rec,0,sizeof(rec)); memset(num,0,sizeof(num)); memset(dp,0,sizeof(dp)); int last=-1,tmp,cnt=0; for(int i=1;i<=n;i++){ scanf("%d",&tmp); if(tmp==last) num[cnt]++; else{ rec[++cnt]=tmp; num[cnt]++; last=tmp; } } printf("Case %d: %d\n",++ct,solve(1,cnt,0)); } }
//POJ2955 #include<stdio.h> #include<string.h> #include<algorithm> using namespace std; int dp[105][105],n; char str[105]; int main(){ while(scanf("%s",str)){ if(str[0]=='e') break; memset(dp,0,sizeof(dp)); n=strlen(str); for(int l=1;l<n;l++){ for(int i=0;i+l<n;i++){ int j=i+l; if((str[i]=='('&&str[j]==')')||(str[i]=='['&&str[j]==']')) dp[i][j]=dp[i+1][j-1]+2; for(int k=i;k<j;k++){ dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]); } } } printf("%d\n",dp[0][n-1]); } }
状态压缩是一类问题的通用思想。当一个问题不能用单一的状态思考时,将多个状态压缩为k进制的数(k为对每个点来说,可选择的状态数),最终状态数为k^n。状态压缩常用于搜索或者DP问题中。而且一个特征是n和m的范围在[12,20]区间,小点的话暴搜可以过。
状态常借助二进制位运算来判断和转移:
对于一个状态sup,从其中找到状态子集的方法:
当状态压缩用于DP中时,就成了状压DP。关键在于完成状态压缩,之后按照普通DP来确定状态量个转移方程即可。由于kn,理论复杂度通常比较大,但是一般有状态剪枝操作,可以简化复杂度。状压DP的一个特点是,通常需要对各种状态进行循环(这话比较抽象),而且将可能的剪枝操作尽可能放到上层循环中尽可能降低复杂度。例题洛谷P1433,P2704,P4011.
//P1433,旅行商问题,由n!变为2^n //并不一定必须要求每个点只能经过一次,多次的话由二进制变为k进制(但是位运算也不能在k进制下用了) #include<bits/stdc++.h> using namespace std; int n; struct node { double x,y; }nodes[17]; double f[17][35000],x[17],y[17]; double getDis(int a,int b) { return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b])); } int main() { scanf("%d",&n); //nodes[0].x=0; //nodes[0].y=0; x[0]=y[0]=0; for(int i=1;i<=n;i++){ scanf("%lf%lf",&x[i],&y[i]); } memset(f,127,sizeof(f)); for(int s=1;s<=(1<<n)-1;s++) { for(int i=1;i<=n;i++){ if((s&(1<<(i-1)))==0){ continue; } if(s==(1<<(i-1))){ f[i][s]=0; continue; } for(int j=1;j<=n;j++){ if(s&(1<<(j-1))==0||i==j){ continue; } f[i][s]=min(f[i][s],f[j][s-(1<<(i-1))]+getDis(i,j)); } } } double ans=-1; for(int i=1;i<=n;i++){ double t=f[i][(1<<n)-1]+getDis(i,0); if(ans==-1||t<ans){ ans=t; } } printf("%.2lf\n",ans); return 0; }
P2704,这类问题常常是图上求最短路,但是有的路径要求有一定的状态才能通过。类似的还有POJ3411
//P2704 #include<bits/stdc++.h> using namespace std; int n,m,line[105],num[1050]; long long dp[5][1050][1050],ans; string str; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ cin>>str; for(int j=0;j<m;j++){ if(str[j]=='H') line[i]=(line[i]<<1)+1; else if(str[j]=='P') line[i]=(line[i]<<1); } } int maxstate=1<<m; for(int i=0;i<maxstate;i++){ int tmp=i; while(tmp!=0){ if(tmp%2==1) num[i]++; tmp/=2; } } for(int i=1;i<=n;i++){ //sum[i%3]=0; for(int j=0;j<maxstate;j++){ if(j&line[i]) continue; if(((j<<1)&j)!=0||((j<<2)&j)!=0||((j>>1)&j)!=0||((j>>2)&j)!=0) continue; for(int k=0;k<maxstate;k++){ if(k&line[i-1]) continue; if(((k<<1)&k)!=0||((k<<2)&k)!=0||((k>>1)&k)!=0||((k>>2)&k)!=0) continue; if((j&k)==0){ for(int l=0;l<maxstate;l++){ if(l&line[i-2]) continue; if(((l<<1)&l)!=0||((l<<2)&l)!=0||((l>>1)&l)!=0||((l>>2)&l)!=0) continue; if((k&l)==0&&(j&l)==0){ dp[i%3][j][k]=max(dp[i%3][j][k],num[j]+dp[(i+2)%3][k][l]); } } } } } } for(int i=0;i<maxstate;i++){ for(int j=0;j<maxstate;j++){ ans=max(ans,dp[n%3][i][j]); } } printf("%lld\n",ans); return 0; }
POJ2441,容易看出是状压DP,入手题,多关注优化的思路
#include<stdio.h> #include<string.h> int n,m,rec[25][25],dp[3000000]; int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%d",&rec[i][0]); for(int j=1;j<=rec[i][0];j++) scanf("%d",&rec[i][j]); } memset(dp,0,sizeof(dp)); dp[0]=1; int maxstate=(1<<m)-1,ans=0; for(int i=1;i<=n;i++){ for(int s=maxstate;s>=0;s--){ if(dp[s]){ for(int j=1;j<=rec[i][0];j++){ if((s&(1<<(rec[i][j]-1)))==0){ dp[s|(1<<(rec[i][j]-1))]+=dp[s]; } } } dp[s]=0; } } for(int i=0;i<=maxstate;i++){ ans+=dp[i]; } printf("%d\n",ans); }
POJ2836,主要是对问题的分析,把握住思路之后就比较好做了
#include<stdio.h> #include<algorithm> #include<string.h> #include<math.h> using namespace std; int n,cnt,dp[500000]; struct node{ int x,y; }nodes[20]; struct rect{ int c,s; }rects[400]; int main(){ while(scanf("%d",&n)!=EOF){ if(n==0) break; cnt=0; memset(dp,0x3f,sizeof(dp)); for(int i=1;i<=n;i++){ scanf("%d%d",&nodes[i].x,&nodes[i].y); } for(int i=1;i<=n;i++){ for(int j=1;j<i;j++){ int h=abs(nodes[i].y-nodes[j].y)==0?1:abs(nodes[i].y-nodes[j].y); int w=abs(nodes[i].x-nodes[j].x)==0?1:abs(nodes[i].x-nodes[j].x); rects[cnt].c=h*w; rects[cnt].s=0; for(int k=1;k<=n;k++){ if(nodes[k].x>=min(nodes[i].x,nodes[j].x)&&nodes[k].x<=max(nodes[i].x,nodes[j].x) &&nodes[k].y>=min(nodes[i].y,nodes[j].y)&&nodes[k].y<=max(nodes[i].y,nodes[j].y)){ rects[cnt].s|=1<<(k-1); } } cnt++; } } int maxstate=(1<<n)-1; dp[0]=0; for(int s=0;s<=maxstate;s++){ if(dp[s]!=0x3f3f3f3f){ for(int i=0;i<cnt;i++){ int state=s|rects[i].s; if(state!=s){ dp[state]=min(dp[state],dp[s]+rects[i].c); } } } } printf("%d\n",dp[maxstate]); } return 0; }
#include<stdio.h>#include<string.h>int n,m,rec[25][25],dp[3000000];int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%d",&rec[i][0]); for(int j=1;j<=rec[i][0];j++) scanf("%d",&rec[i][j]); }
memset(dp,0,sizeof(dp)); dp[0]=1; int maxstate=(1<<m)-1,ans=0; for(int i=1;i<=n;i++){ for(int s=maxstate;s>=0;s--){ if(dp[s]){ for(int j=1;j<=rec[i][0];j++){ if((s&(1<<(rec[i][j]-1)))==0){ dp[s|(1<<(rec[i][j]-1))]+=dp[s]; } } } dp[s]=0; } } for(int i=0;i<=maxstate;i++){ ans+=dp[i]; } printf("%d\n",ans);}