擦除字符串 | ||||||
| ||||||
Description | ||||||
给出一个字符串s,我们每一步可以擦除当前字符串中的一个回文子串。现在问最少需要多少步才能把整个字符串擦除。 例如我们能在一步里面擦除abcba从axbyczbea并且得到xyze。 | ||||||
Input | ||||||
第一行是一个整数T,代表T组测试数据,对于每组测试数据输入一个长度<=16的字符串。 | ||||||
Output | ||||||
对于每组测试数据输出最少的步数。 | ||||||
Sample Input | ||||||
2 aa abb | ||||||
Sample Output | ||||||
1 2 |
状压DP。
我们可以反过来看,将题目看成是从空串添加回文串,直至成为原串。
我们先枚举出原串中所有的回文串,虽然时间复杂度较高,但是字符串不长,所以无碍。
用一个dug数组,将回文串状态压缩成一个数,i 状态是回文串,则dug[i] = 1。比如:
原串:f g h j i u h g f
那么f g h i h g f 是一个回文串
二进制数 1 1 1 0 1 0 1 1 1 = 471
记dug[ 471 ] = 1
记增添回文串到状态i的最小次数为dp[ i ]
然后我们将每个状态进行枚举,如果这个状态 q 与前一个状态 i 比较下,是回文串的话,那么取最小值,即dp[q]=min(dp[q],dp[i]+1)。
第一次用的是记忆化搜索,严重超时!但是代码中记忆化搜索的部分没删,注释掉了。
AC代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char s[17];
int dp[1<<17];
bool dug[1<<17];
void check(int len)
{
for(int i=1;i<(1<<len);i++)
{
int flag=0;
int k=len;
for(int j=0;j<k;)
{
while(((1<<j)&i)==0)
{
j++;
if(j>len)
{
break;
}
}
while(((1<<k)&i)==0)
{
k--;
if(k<0)
{
break;
}
}
if(j<=k)
{
if(s[j]!=s[k])
{
flag=1;
break;
}
j++;
k--;
}
}
if(!flag)
{
dug[i]=1;
}
}
return;
}
void dfs(int z,int ans,int len,int maxlen)
{
if(z==0)
{
if(dp[z]>ans)
dp[z]=ans;
return;
}
else
{
for(int i=len;i>=0;i--)
{
if((z&(1<<i))>0)
{
len=i+1;
break;
}
}
for(int i=1;i<(1<<len);i++)
{
if(dug[i]&&(((z^i)&(~z))==0))
{
//printf("$%d\n",i);
if(dp[z^i]>ans+1)
{
dp[z^i]=ans+1;
}
else
{
ans=dp[z^i]-1;
}
dfs(z^i,ans+1,len,maxlen);
}
}
}
}
/*
101101 z
101000 i
000101 z^i
*/
/*
011010 ~z
100101 z
000111 i
100010 z^i
*/
int main()
{
int t;
scanf("%d",&t);
getchar();
while(t--)
{
scanf("%s",s);
int len =strlen(s);
memset(dug,0,sizeof(dug));
memset(dp,0x3f3f3f3f,sizeof(dp));
check(len);
//dfs((1<<len)-1,0,len,len);
//记忆化搜索会超超超超时……
int end=1<<len;
dp[0]=0;
for(int i=0;i<end;i++)
{
int x=i^(end-1);
for(int j=x;j!=0;j=(j-1)&x)
{
if(dug[j]==0)continue;
int q=i|j;
dp[q]=min(dp[q],dp[i]+1);
}
}
printf("%d\n",dp[end-1]);
}
return 0;
}