题目
有T个字符串Si。
一开始,字符串S是空的。
可以进行2中操作:①序列头/尾加字符;②整个字符串S复制一次,反向粘贴在S后面。
问最少经过几次操作,空串变成Si。
题解
对于
的数据,记忆化搜索即可。
目前起点是Si,可以任意从头或尾删字符,或对折。
求变成空串所需要的最少步数。
100%?
回文树。涉及到回文的问题,可用回文树解决。
回文树中每个点都是回文串,又涉及步数问题,不妨用DP解决。
怎么找突破口?
实际上,最后得出的Si,都可以看作是操作到某个串,然后进行操作①。
设
表示得到字符串x所需要的最少步骤。
则
显然,长度为奇数的回文串,它的答案为字符串长。
考虑字符串x经过谁转移而来。
设x去掉头尾2个字符的串为y,则
设x的长度不超过
最长回文后缀为z,则
两种转移取个min即可。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 100010
#define LL long long
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int i,j,k,l,n,m,ans;
int f[N],qu[N];
int Min(int x,int y){return x<y?x:y;}
struct PAM{
int pam[N][4],len[N],st[N],fail[N],pre[N],gs,L,wz;
void clear(){
memset(st,0,sizeof(st));
memset(pam,0,sizeof(pam));
memset(fail,0,sizeof(fail));
memset(len,0,sizeof(len));
memset(pre,0,sizeof(pre));
len[1]=-1;fail[0]=1;st[0]=-1;gs=1;L=0;
}
int F(int x){for(;st[L-len[x]-1]!=st[L];x=fail[x]);return x;}
int Fg(int x,int y){
if(len[y]<=2)return x;
for(;len[x]*2>len[y];x=fail[x]);
return x;
}
void Extend(int c){
st[++L]=c;
int np=F(wz),nq;
if(!pam[np][c]){
len[nq=++gs]=len[np]+2;
fail[nq]=pam[F(fail[np])][c];
pam[np][c]=nq;
}
wz=pam[np][c];
}
int cz(int x){
if(x==0)return 0;
if(x==2)return 1;
if(x==6)return 2;
return 3;
}
void Count(){
int i,x,y,l=0,r=1;
memset(pre,-1,sizeof(pre));
ans=L;
f[0]=1;
fo(i,2,gs)if(len[i]&1)f[i]=len[i];
qu[1]=0;
while(l<r){
x=qu[++l];
fo(i,0,3)if(pam[x][i]){
y=pam[x][i];
if(!(~pre[y]))pre[y]=Fg(fail[y],y);
f[y]=Min(f[x]+1,f[pre[y]]+1+(len[y]>>1)-len[pre[y]]);
ans=Min(ans,L-len[y]+f[y]);
qu[++r]=y;
}
}
}
void Work(){
char ch=getchar();
while(ch<'A'||ch>'Z')ch=getchar();
while(ch>='A'&&ch<='Z')Extend(cz(ch-'A')),ch=getchar();
Count();
}
}P;
int main(){
scanf("%d",&n);
while(n--){
P.clear();
P.Work();
printf("%d\n",ans);
}
return 0;
}