传送门
回文自动机的好题啊
先建一个回文自动机,然后记$dp[i]$表示转移到$i$节点代表的回文串的最少的需要次数
首先肯定2操作越多越好,经过2操作之后的串必定是一个回文串,所以最后的答案肯定是由一个回文串+不断暴力添加得来,那么答案就是$min(ans,dp[i]+n-len[i])$
然后对于一个串$i$,如果它在前面和后面加上一个字母可以形成回文串$j$,则$dp[j]=dp[i]+1$
为啥嘞?我们可以假设在形成$i$的之前一步把这个字母加上去,执行2操作后就可以变成$j$了
然后我们可以fail指针找到最长的回文串$x$满足$len[x]<=len[i]/2$,那么$dp[i]=min(dp[i],dp[x]+1+len[i]/2-len[x])$(先暴力填好一半,剩下的用2操作)
然后可以用队列记录状态,保证转移至有序的
至于怎么找$x$,我们可以直接在建自动机的时候顺便求出来,就是多跳几次。这个看代码好了
1 //minamoto 2 #include3 #include 4 template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;} 5 const int N=2e5+5,M=5; 6 char s[N];int dp[N],len[N],fail[N],ch[N][M]; 7 int trans[N],last,p,q,str[N],tot,ans,n,qu[N]; 8 int val[105]; 9 inline int newnode(int x){ 10 len[++tot]=x;memset(ch[tot],0,sizeof(ch[tot])*5);return tot; 11 } 12 inline int getfail(int x,int n){ 13 while(s[n-len[x]-1]!=s[n]) x=fail[x];return x; 14 } 15 inline void init(){ 16 val['A']=0,val['T']=1,val['C']=2,val['G']=3; 17 s[0]=-1,fail[0]=1,last=0; 18 len[0]=0,len[1]=-1,tot=1; 19 memset(ch[0],0,sizeof(int)*5),memset(ch[1],0,sizeof(int)*5); 20 } 21 void ins(int c,int i){ 22 p=getfail(last,i); 23 if(!ch[p][c]){ 24 q=newnode(len[p]+2); 25 fail[q]=ch[getfail(fail[p],i)][c]; 26 ch[p][c]=q; 27 if(len[q]<=2) trans[q]=fail[q]; 28 else{ 29 int tmp=trans[p]; 30 while(s[i-1-len[tmp]]!=s[i]||(len[tmp]+2)*2>len[q]) tmp=fail[tmp]; 31 trans[q]=ch[tmp][c]; 32 } 33 } 34 last=ch[p][c]; 35 // printf("%d\n",last); 36 } 37 int main(){ 38 // freopen("testdata.in","r",stdin); 39 int T;scanf("%d",&T); 40 while(T--){ 41 scanf("%s",s+1); 42 init(),ans=n=strlen(s+1); 43 for(int i=1;i<=n;++i) ins(val[s[i]],i); 44 for(int i=2;i<=tot;++i) dp[i]=len[i]; 45 int h=1,t=0;qu[++t]=0,dp[0]=1; 46 while(h<=t){ 47 int u=qu[h++]; 48 for(int i=0;i<4;++i){ 49 int x=ch[u][i]; 50 if(!x) continue; 51 dp[x]=dp[u]+1; 52 int y=trans[x]; 53 cmin(dp[x],dp[y]+1+len[x]/2-len[y]); 54 cmin(ans,dp[x]+n-len[x]); 55 qu[++t]=x; 56 } 57 } 58 printf("%d\n",ans); 59 } 60 return 0; 61 }