擦除字符串 | ||||||
| ||||||
Description | ||||||
给出一个字符串s,我们每一步可以擦除当前字符串中的一个回文子串。现在问最少需要多少步才能把整个字符串擦除。 例如我们能在一步里面擦除abcba从axbyczbea并且得到xyze。 | ||||||
Input | ||||||
第一行是一个整数T,代表T组测试数据,对于每组测试数据输入一个长度<=16的字符串。 | ||||||
Output | ||||||
对于每组测试数据输出最少的步数。 | ||||||
Sample Input | ||||||
2 aa abb | ||||||
Sample Output | ||||||
1 2 |
思路:
1、通过Dfs枚举所有子序列状态,并判断当前枚举出来的状态是否是一个可行状态(是否是一个回文串)因为字符串并不会很长,所以时间复杂度:O(2^len*len)的时间复杂度虽然看起来很大,其实最坏也就是2^16*16==2^20.对应枚举出来的状态i如果是回文串,我们vis【i】=1.标记此种状态为回文串。
2、接下来我们设定dp【i】表示从状态0添加回文串到状态i需要的步数。虽然我们问题上说的是让我们从一个字符串去擦除回文串需要的最少步数,而因为位运算操作之类的方便角度出发,我们其实可以将擦除回文串变成增加回文串。
3、那么dp【0】=0;ans=dp【(1<<len)-1】;其状态转移方程:dp【q】=min(dp【q】,dp【i】+1);当然,q是i在加了某个回文串之后的状态。有了状态转移方程,分析其时间复杂度,如果i,q两个状态都通过直接枚举的话,是会达到:O(1<<len*1<<len)的时间复杂度,最坏也达到了2^16*2^16==2^32显然会超时。那么我们做简单优化.
4、如果当前枚举到的i状态:首先我们将i状态按位取反得到状态tmp,然后我们判断一下这个tmp是不是一个回文串,如果是(vis【i】==1),那么状态q==tmp|i,然后tmp--。那么是不是优化到这里我们就完事大吉了呢?显然优化的还不够彻底,我们继续优化、对应状态tmp,判断过后,我们明显需要tmp--,对下一个状态进行判断,那么对应如果下一个tmp的某一位有1,而状态i也有1,显然这种情况是不需要的(重叠上了字符串,明显tmp现在非法),而且如果这个1所在位为最高位,那么将会枚举了(1<<(len-1))这些不需要枚举的情况,那么我们可以进行优化:tmp--之后,tmp&x.令其中不需要出现的1去掉、这样就能做到极大的优化。
5、思路建立的差不多过后,我们剩下的工作就是写代码啦。细节处理部分可以参考代码;
Ac代码:
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
int n;
int dp[1<<17];
int vis[1<<17];
char a[50];
void Dfs(int cur,int tmp[],int sum)
{
if(cur==n)
{
char aa[17];
int cont=0;
for(int i=0;i<n;i++)
{
if(tmp[i]==1)
{
aa[cont++]=a[i];
}
}
int flag=0;
for(int i=0;i<cont/2;i++)
{
if(aa[i]==aa[cont-i-1])continue;
else
{
flag=1;
break;
}
}
if(flag==0)vis[sum]=1;
return ;
}
for(int i=0;i<2;i++)
{
tmp[cur]=i;
Dfs(cur+1,tmp,sum+i*(1<<(n-cur-1)));
}
}
void getmap()
{
memset(vis,0,sizeof(vis));
n=strlen(a);
int tmp[20];
Dfs(0,tmp,0);
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%s",a);
getmap();
memset(dp,0x3f3f3f3f,sizeof(dp));
int end=1<<n;
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(vis[j]==0)continue;
int q=i|j;
dp[q]=min(dp[q],dp[i]+1);
}
}
printf("%d\n",dp[end-1]);
}
}