Description
Input
Output
字符串的构建过程一定是先构建出一个回文串,然后剩余字符一个个加到前面和后面.
考虑构建回文自动机,那么对于自动机中的点 $x$,有 $f[x]$ 表示达到该目标最少操作次数.
如果我们能求出 $f[x]$,那么答案就等于 $n+f[x]-len[x]$ 中最小的一个.
考虑如何求解 $f[x]:$
假如说 $x->y$ 有一条字符转移边,那么 $f[y]\leqslant f[x]$,即可以将 $f[x]$ 的值赋给 $f[y]$.
我们在构建自动机的时候再构建一个 $trans[x]$ 指针,来表示第一个长度小于等于 $len[x]/2$ 且是 $x$ 的回文后缀的位置.
那么就有 $f[x]=f[trans[x]]+1+\frac{len[x]}{2}-\frac{len[trans[x]]}{2}$
由于回文串的长度肯定是偶数的,所以我们只需扩展 $0$ 的所有出边即可,不必扩展 $1$ 的出边.
扩展的时候 BFS 即可.
#include <cstdio> #include <string> #include <queue> #include <cstring> #define N 100007 #define setIO(s) freopen(s".in","r",stdin) using namespace std; char S[N]; queue<int>q; int tot,last,n; int ch[N][4],len[N],pre[N],ss[N],trans[N],f[N]; void Initialize() { pre[0]=1,len[1]=-1,ss[0]=-1,tot=1; } int newnode(int x) { return len[++tot]=x,tot; } int getfail(int p,int i) { while(ss[i-len[p]-1]!=ss[i]) p=pre[p]; return p; } void extend(int c,int i) { int p=getfail(last,i); if(!ch[p][c]) { int q=newnode(len[p]+2); pre[q]=ch[getfail(pre[p],i)][c],ch[p][c]=q; if(len[q]<=2) trans[q]=pre[q]; else { int tmp=trans[p]; while(ss[i-len[tmp]-1]!=ss[i]||((len[tmp]+2)<<1)>len[q]) tmp=pre[tmp]; trans[q]=ch[tmp][c]; } } last=ch[p][c]; } void clr() { for(int i=0;i<=tot;++i) { pre[i]=len[i]=0; memset(ch[i],0,sizeof(ch[i])); } tot=0; } void solve() { int i,j,ans; scanf("%s",S+1),n=strlen(S+1),ans=n,Initialize(); for(i=1;i<=n;++i) { if(S[i]=='A') ss[i]=0; if(S[i]=='G') ss[i]=1; if(S[i]=='C') ss[i]=2; if(S[i]=='T') ss[i]=3; } for(i=1;i<=n;++i) extend(ss[i],i); for(i=2;i<=tot;++i) f[i]=len[i]; for(i=0;i<4;++i) if(ch[0][i]) q.push(ch[0][i]); while(!q.empty()) { int x=q.front();q.pop(); f[x]=min(f[x],f[trans[x]]+1+len[x]/2-len[trans[x]]); ans=min(ans,n-len[x]+f[x]); for(i=0;i<4;++i) { if(!ch[x][i]) continue; int y=ch[x][i]; f[y]=min(f[y],f[x]+1); q.push(y); } } printf("%d\n",ans),clr(); } int main() { // setIO("input"); int T; scanf("%d",&T); while(T--) solve(); return 0; }