擦除字符串 | ||||||
|
||||||
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;
}