题目描述
回文自动机
首先,我们可以认为,每个最终串都是进行若干操作后进行了一次第二个操作后,再在两端插入而得到的。
先对最终串插入回文自动机上,然后做动态规划。
我们设f[i]表示i这个结点所代表的回文串最后一次操作是第二个操作需要多少个操作得到。
注意偶数树的根节点f值为1。
fa[i]表示其在回文树上的父亲,pre[i]表示其最长长度不超过其的长度的一半的回文后缀。pre怎么求呢?显然沿着fail一直跳跳到第一个长度符合要求的即可。
然而这样找pre的复杂度是玄学的(是的能过但是我们思索如果是一堆a呢?),所以其实正解估计是倍增。
如果结点i是奇回文串,f[i]=len[i]。
否则f[i]=min(f[fa[i]]+1,f[pre[i]]+len[i]/2-len[pre[i]]+1)
答案怎么求?
枚举回文树上每个结点,然后ans=min(ans,n-len[i]+f[i])
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+10;
int f[maxn],g[maxn][5],fail[maxn],pre[maxn],fa[maxn],len[maxn];
int i,j,k,l,t,n,m,tot,ca,ans,last;
char ch;
char s[maxn];
void insert(int c){
int x=last;
while (s[i-len[x]-1]!=s[i]) x=fail[x];
if (!g[x][c]){
fa[++tot]=x;
len[tot]=len[x]+2;
int y=fail[x];
while (s[i-len[y]-1]!=s[i]) y=fail[y];
fail[tot]=g[y][c];
y=g[y][c];
while (len[y]>len[tot]/2) y=fail[y];
pre[tot]=y;
if (len[tot]%2==0) f[tot]=min(f[fa[tot]]+1,f[pre[tot]]+len[tot]/2-len[pre[tot]]+1);
else f[tot]=len[tot];
g[x][c]=tot;
}
last=g[x][c];
}
int main(){
//freopen("dna4.in","r",stdin);
scanf("%d",&ca);
while (ca--){
scanf("%s",s+1);
n=strlen(s+1);
ans=n;
fail[0]=fail[1]=tot=1;
len[1]=-1;
f[1]=2;
f[0]=1;
fo(i,0,n)
fo(j,0,3)
g[i][j]=0;
last=0;
fo(i,1,n){
if (s[i]=='A') t=0;
else if (s[i]=='T') t=1;
else if (s[i]=='C') t=2;else t=3;
insert(t);
}
fo(i,2,tot) ans=min(ans,n-len[i]+f[i]);
printf("%d\n",ans);
}
}