ZOJ 3675 Trim the Nails
用一把宽度为n的指甲钳,去剪宽度为m的指甲,指甲钳可以正反面使用。用最少的次数剪完指甲。
月赛的时候,我大致觉得是状态压缩,可是没有想到比较快的方法。大约50来分钟的时候,cl123神奇地1Y了……
赛后看了下人家代码,貌似是一个贪心,但他的代码实际是错的……比如
10 *.******.* 6
人家的程序输出的是2,正解是1……
今天上课的时候又想了下,状态压缩的确是可以做的。
首先把 (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还需要分正反情况考虑。
AC代码如下。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 using namespace std; 5 6 #define INF 0x3f3f3f3f 7 int dp[1 << 20]; 8 9 int main() { 10 int n, m; 11 while (~scanf("%d", &n)) { 12 char cliper[25] = {0}, rev[25] = {0}; 13 int bin = 0, rbin = 0; 14 memset(dp, 0x3f, sizeof(dp)); 15 scanf("%s%d", cliper, &m); 16 for (int i = 0; i < n; i++) 17 rev[i] = cliper[n - 1 - i]; 18 for (int i = 0; i < n; i++) { 19 bin = bin << 1; 20 rbin = rbin << 1; 21 if (cliper[i] == '*') bin |= 1; 22 if (rev[i] == '*') rbin |= 1; 23 } 24 dp[(1 << m) - 1] = 0; 25 for (int i = (1 << m) - 1; i >= 0; i--) { 26 if (dp[i] == INF) continue; 27 for (int j = 0; j < m; j++) { 28 dp[i & (~(bin << j))] = min(dp[i & (~(bin << j))], dp[i] + 1); 29 dp[i & (~(rbin << j))] = min(dp[i & (~(rbin << j))], dp[i] + 1); 30 dp[i & (~(bin >> j))] = min(dp[i & (~(bin >> j))], dp[i] + 1); 31 dp[i & (~(rbin >> j))] = min(dp[i & (~(rbin >> j))], dp[i] + 1); 32 } 33 } 34 if (dp[0] < INF) printf("%d\n", dp[0]); 35 else printf("-1\n"); 36 } 37 return 0; 38 }