hdu 4628 Pieces

题目大意:给定一个字符串,要求每次删去其中的回文子序列(可以不连续),问最少删除几次,可以将串清空,例:abb,依次删去bb,a或a,bb 需要2次

解法:

  比赛的时候考虑了贪心,即每次删去最长的,但存在反例:abcddcbacd,若删去abcddcba,需要3次,其实最少2次,abcdcba,dcd;然后又考虑搜索,syc写了,不过最后没写出来,比赛的时候确实没有想到更好的做法;

  正确的做法是状态压缩DP,长度只有16,最多2^16个状态,dp[x]表示x这个状态最少需要多少次,那么dp[x]=min(dp[x],dp[k]),k为x的子集,每个状态的初始值,需要根据其特征来定,如果本身就是一个回文串,那么dp[x]=1,否则为此状态下的长度;

  如何枚举子集?利用位运算,巧妙的降低编程复杂度,for(sub=ori; sub; sub=(sub-1)&ori),那么dp[ori]=min(dp[ori],dp[sub]+dp[sub^ori]),其中sub^ori是被删去的部分,代码如下:

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <vector>
 4 #include <algorithm>
 5 using namespace std;
 6 
 7 int l, dp[1<<17];
 8 char s[20];
 9 vector <int> q;
10 int init(int x)
11 {
12     q.clear();
13     for (int i=0; i<=strlen(s); i++)
14         if (x & (1<<i)) q.push_back(i);
15     for (int i=0; i<q.size()/2; i++)
16         if (s[q[i]] != s[q[q.size()-i-1]]) return q.size();
17     return 1;
18 }
19 
20 int main()
21 {
22     int T; scanf("%d\n", &T);
23     while ( T -- )
24     {
25         scanf("%s", s);
26         l = 1<<strlen(s);
27         for (int i=1; i<=l-1; i++)
28         {
29             dp[i] = init(i);
30             for (int j=i; j; j=(j-1)&i) //位运算取子集 
31                 dp[i] = min(dp[i], dp[j]+dp[j^i]);    //j^i为删去的部分 
32         }
33         printf("%d\n", dp[l-1]);
34     }
35     return 0;
36 }
View Code

 

posted @ 2013-07-31 14:52  sxqqslf  阅读(203)  评论(0编辑  收藏  举报