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是被删去的部分,代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }