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代码如下。

View Code
 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 }
posted @ 2012-11-27 19:44  dgsrz  阅读(488)  评论(0编辑  收藏  举报