zoj 3675 剪指甲 状态压缩dp
学习了博客 http://www.cnblogs.com/dgsrz/articles/2791363.html
首先把 (1<<m)-1 作为指甲没剪时的初态(全是1),dp[x]保存的就是对于每个状态,需要剪的最少次数。
当前状态x以二进制表示时,出现的1表示这一位置还留有指甲,0就是已剪去。而对于指甲钳,又可以将其以二进制表示,对于案例:****..**,不妨用11110011代替,1表示当前位置刀锋完好,0相反。于是对每一位分析:
原来指甲情况 A | 指甲钳刀锋 B | 最终指甲情况 Y |
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 1 |
1 | 1 | 0 |
化简一下得到每一位上的关系:Y = A & ~B
所以递推方程就是:dp[x & (~B)] = min(dp[x & (~B)], dp[x] + 1);
问题是,指甲钳并不总是和指甲最左端对齐,所以还需要对指甲钳进行移动。反应在上式,就是对B进行左右各m次的移位操作。另外,指甲钳可以反着用,于是B还需要分正反情况考虑。
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 using namespace std; 5 6 #define INF 0x3f3f3f3f 7 int dp[1 << 20]; 8 char s[20],rev[20]; 9 10 int main() { 11 12 int n,m,left,right; 13 while(~scanf("%d%s%d",&n,s,&m)) 14 { 15 for(int i=0;i<n;i++) 16 rev[i]=s[n-i-1]; 17 left=0; right=0; 18 for(int i=0;i<n;i++) 19 { 20 left=left<<1; right=right<<1; 21 if(s[i]=='*') 22 left|=1; 23 if(rev[i]=='*') 24 right|=1; 25 } 26 for(int i=0;i<1<<m;i++) 27 dp[i]=INF; 28 dp[(1<<m)-1]=0; 29 for(int i=(1<<m)-1;i>=0;i--) 30 { 31 if(dp[i]==INF)// 由i 剪到 i&(~(left<<j)的状态 若 。。 说明这还没被剪过 32 continue; 33 for(int j=0;j<m;j++) 34 { 35 dp[i&(~(left<<j))]=min(dp[i&(~(left<<j))],dp[i]+1); 36 dp[i&(~(left>>j))]=min(dp[i&(~(left>>j))],dp[i]+1); 37 dp[i&(~(right<<j))]=min(dp[i&(~(right<<j))],dp[i]+1); 38 dp[i&(~(right>>j))]=min(dp[i&(~(right>>j))],dp[i]+1); 39 } 40 } 41 if(dp[0]<INF) 42 printf("%d\n",dp[0]); 43 else printf("-1\n"); 44 } 45 return 0; 46 }