状态DP
1.poj 1185
题意:炮兵阵地有n行m列,n<=100,m<=10,故可以使用将每行放置大炮的情况状态压缩为二进制的形式。
状态数目最多约为60种,dp[r][i][k]表示在r行状态为k,r-1行状态为i,前r行最多可以摆放的大炮数目。
之后枚举r行
状态转移:dp[r][j][i] = max{dp[r][j][i],dp[r-1][k][j]+sum[i]}
最为关键的是将题目模型化嵌套入状态压缩的模型,而且要熟悉各种位运算的用法。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 6 using namespace std; 7 8 int cnt,map[105],dp[105][65][65],sum[65],stk[65]; 9 10 int ok(int x) 11 { 12 if(x&(x<<1)||x&(x<<2)) 13 return 0; 14 return 1; 15 } 16 17 int add(int x) 18 { 19 return x==0?0:add(x-(x&-x))+1; 20 } 21 22 void init(int row,int col) 23 { 24 int r,c; 25 char temp; 26 memset(map,0,sizeof(map)); 27 for(r=0;r<row;r++) 28 { 29 getchar(); 30 for(c=0;c<col;c++) 31 { 32 scanf("%c",&temp); 33 if(temp=='H') 34 map[r]|=(1<<c); 35 } 36 } 37 } 38 39 void get(int n) 40 { 41 int i; 42 cnt=0; 43 for(i=0;i<(1<<n);i++) 44 { 45 if(ok(i)) 46 { 47 stk[cnt]=i; 48 sum[cnt++]=add(i); 49 } 50 } 51 } 52 53 int main() 54 { 55 int row,col,i,j,r,c,k,ans; 56 while(scanf("%d%d",&row,&col)!=EOF) 57 { 58 ans=0; 59 memset(dp,-1,sizeof(dp)); 60 init(row,col); 61 get(col); 62 for(i=0;i<cnt;i++) 63 if(!(stk[i]&map[0])) 64 dp[0][0][i]=sum[i]; 65 for(r=1;r<row;r++) 66 { 67 for(i=0;i<cnt;i++) 68 { 69 if(stk[i]&map[r]) 70 continue; 71 for(j=0;j<cnt;j++) 72 { 73 if(stk[j]&stk[i]) 74 continue; 75 for(k=0;k<cnt;k++) 76 { 77 if(stk[k]&stk[i]) 78 continue; 79 if(dp[r-1][k][j]==-1) 80 continue; 81 dp[r][j][i]=max(dp[r][j][i],dp[r-1][k][j]+sum[i]); 82 } 83 } 84 } 85 } 86 for(i=0;i<cnt;i++) 87 for(j=0;j<cnt;j++) 88 { 89 if(ans<dp[row-1][i][j]) 90 ans=dp[row-1][i][j]; 91 } 92 printf("%d\n",ans); 93 } 94 return 0; 95 }
2.poj 3254
题意:其实就是炮兵阵地的简单版,实质上算是枚举了一遍所有状态的组合,从而求得总的方案数。
在学习时要多思考,敲码实现与做的题的多少都没有关系,最重要的是通过做题去深刻理解一个算法的思想,并学会运用这些思想。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 6 using namespace std; 7 const int MAX=400,MOD=100000000; 8 9 int cnt,stk[MAX],map[MAX],dp[15][MAX]; 10 int ok(int n) 11 { 12 if(n&n<<1) 13 return 0; 14 return 1; 15 } 16 17 void get(int n) 18 { 19 int i; 20 cnt=0; 21 for(i=0;i<(1<<n);i++) 22 { 23 if(ok(i)) 24 stk[cnt++]=i; 25 } 26 } 27 28 int main() 29 { 30 int row,col,r,c,x,i,j,sum; 31 while(scanf("%d%d",&row,&col)!=EOF) 32 { 33 sum=0; 34 memset(map,0,sizeof(map)); 35 memset(dp,0,sizeof(dp)); 36 for(r=0;r<row;r++) 37 for(c=0;c<col;c++) 38 { 39 scanf("%d",&x); 40 if(x==0) 41 map[r]|=(1<<c); 42 } 43 get(col); 44 for(i=0;i<cnt;i++) 45 if(!(map[0]&stk[i])) 46 dp[0][i]=1; 47 for(r=1;r<row;r++) 48 { 49 for(i=0;i<cnt;i++) 50 { 51 if(map[r]&stk[i]) 52 continue; 53 for(j=0;j<cnt;j++) 54 { 55 if(map[r-1]&stk[j]||stk[i]&stk[j]) 56 continue; 57 dp[r][i]+=dp[r-1][j]; 58 dp[r][i]%=MOD; 59 } 60 } 61 } 62 for(i=0;i<cnt;i++) 63 { 64 sum+=dp[row-1][i]; 65 sum%=MOD; 66 } 67 printf("%d\n",sum); 68 } 69 return 0; 70 }
3.poj 2411
题意:给出一个长为n,宽为m的矩形,要求得出用多个长为2宽为1的小矩形组成该矩形的方案总数。
由于0<n,m<=11, 且求最值问题,故联想到状态DP,此题的难点在于状态的设计,在这里我将横着放置的小矩形用(1 1)横向表示,
竖着放置的小矩形用(0,1),(1,0)纵向表示。判断合法则要根据小矩形放置情况去判定,若在i位置为1,则应判定i+1位置也为1,i+=2;
若为0,则i++;同时要判定i+1>=len;具体看ok()函数。
在第一行时需要得出所有符合条件的状态,之后就需要判断两行之间矩形的组合情况是否合法。这里依然借鉴之前的判断方法,
若两行的i位置均出现1,则两行都存在横着的小矩形,若两行的i位置分别出现0或1,则代表两行之间出现竖着放置的小矩形,若两行
的i位置均出现0,则不符合情况。
最重要的就是巧妙的设计状态表示,以及状态合法情况的判断。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 6 using namespace std; 7 typedef long long ll; 8 int row,col; 9 ll dp[12][3000]; 10 11 int ok(int n) 12 { 13 int i,j,len; 14 len=col; 15 for(i=0;i<len;) 16 { 17 j=n&(1<<i); 18 if(j>0) 19 { 20 if(i+1>=len) 21 return 0; 22 if((n&(1<<(i+1)))==0) 23 return 0; 24 i+=2; 25 } 26 else 27 i++; 28 } 29 return 1; 30 } 31 32 int okk(int n,int m) 33 { 34 int i,x,y,len; 35 len=col; 36 for(i=0;i<len;) 37 { 38 x=n&(1<<i); 39 y=m&(1<<i); 40 if(x==0&&y==0) 41 return 0; 42 if(x>0&&y>0) 43 { 44 if(i+1>=len) 45 return 0; 46 if((n&(1<<(i+1)))==0||(m&(1<<(i+1)))==0) 47 return 0; 48 i+=2; 49 } 50 else 51 i++; 52 } 53 return 1; 54 } 55 56 int main() 57 { 58 int s,r,c,i,j; 59 while(scanf("%d%d",&row,&col)!=EOF) 60 { 61 if(row==0&&col==0) 62 break; 63 if(col>row) 64 swap(col,row); 65 memset(dp,0,sizeof(dp)); 66 s=1<<col; 67 for(i=0;i<s;i++) 68 { 69 if(ok(i)) 70 dp[0][i]=1; 71 } 72 for(r=1;r<row;r++) 73 { 74 for(i=0;i<s;i++) 75 { 76 for(j=0;j<s;j++) 77 { 78 if(okk(i,j)) 79 dp[r][i]+=dp[r-1][j]; 80 } 81 } 82 } 83 printf("%lld\n",dp[row-1][s-1]); 84 } 85 return 0; 86 }
待续...