给出一个字符串,每次可以删去它的回文子序列 例如 abcdbca,你可以删去abba这个子序列,问最少几次能把这个字符串删完。
最多16位,所以最多2^16个状态,而每个状态的转移方程是 dp[i] = min(dp[i], dp[j] + dp[i^j]); 其中j是i的子状态,子状态的定义是i|j == i,那么j就是i的子状态,但是如果这样求子状态,需要从0 到 i-1枚举,有种更巧妙的算法:j = (j - 1) & i;这样就不用每次- -枚举了,直接就是i的子状态。。。MARK一下这个位运算。而i^j则是i状态删去j状态之后所剩下的状态比如1001 ^ 1000就剩下0001了。。。具体的见代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
char str[22];
int dp[777777];
int check(int state)//检测目前状态下,需要删除的子序列是否是回文的
{
char temp[22];
int cnt = 0;
for(int i = 0; i < strlen(str); i++)
{
if ((state>>i) & 1)//把state转换成二进制之后从右往左第i位是否是1
{
cout<<i;
temp[cnt++] = str[i];
}
}
for(int i = 0; i < cnt; i++){
if (temp[i] != temp[cnt-i-1])
{
return cnt;//如果不是回文子序列,那么就需要cnt步才能删除完毕(一个一个字符删除)
}
}
return 1;//如果是,一步就能删除完毕
}
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
scanf("%s", str);
for(int i = 0 ;i < (1<<strlen(str)); i++)
{
dp[i] = check(i);//先判断i状态是否是回文
for(int j = i; j; j = (j-1)&i)//由i的子状态转移过来
{
dp[i] = min(dp[i], dp[j] + dp[i^j]);
}
}
printf("%d\n",dp[(1<<strlen(str)) - 1]);//这个状态就是把所有的都删除
}
return 0;
}