前言
剛學完manacher就來學回文自動機……
感覺好像(板子)也不是很難(背)?
前置知識:Manacher(也不一定非要因為和這個沒啥關系),知道自動機是個啥以及怎么建
簡述
回文樹和回文自動機指的是同一個東西
是由某西伯利亞人於2014夏發明的
這東西主要是用於計數,計算回文串的個數以及種類啥的
建樹
圖我就不放了(太亂了放了也看不懂),要看圖的話可以去這位大神的blog里看一下->這里
不過個人感覺看文字描述應該就會了……吧……
首先,回文樹里有兩棵樹,分別記錄長度為奇數和偶數的回文串
每個節點代表一個回文串,記錄轉移$x$,表示如果在這個回文串前后都加上字符$x$形成的回文串是子節點的子串
然后每一個節點記錄一個fail指針,指向這個回文串的最長后綴回文串
然后我們考慮建樹,假設已經建好了串$s[1...i-1]$,要把字符$s[i]$插入這棵樹
那么每一次只會把$s[1...i]$的最長后綴回文串加進樹里。
證明:(抄這里的)
我們設后綴回文$i$是最長后綴回文$k$的子串,那么$i$肯定關於$k$的回文中心有一個對稱串$j$,由於$k$本身是對稱的,所以$j$和$i$是相同的,那么$j$已經被加入到回文樹中,所以$i$不必再加入
然后就沒問題了。我們設最長回文后綴為$k$,加入字符$c$,那么如果可以,最長回文后綴會變成$ckc$
然而如果$k$之前的字母不是$c$怎么辦?這個時候$fail$指針就派上用場了。我們用$fail$維護每一個節點的最長后綴回文,如果$k$不行,我們看看$k$的最長后綴回文是否可行(就是看$k$的最長后綴回文的前一個字母是否等於$c$),然后就這樣一直跳$fail$指針直到找到為止(如果一直沒有找到會跳到根節點,下面再說)
然后如何維護$fail$呢?我們只要找到了當前節點的最長回文后綴然后記錄一下就就好了
然后字符要接在之前的串的后面,記錄一下$last$表示上一個串的節點
然后注意特殊處理兩個根節點,$0$代表長度為偶數的后綴的根,$1$代表長度為$1$的后綴的根,我們令$fail[0]$指向$1$,$len[1]=-1$,然后令$s[0]=-1$(或任何一個不在原串中出現的字符)($len$代表這個節點的串長)
就比如說如果跳的時候一直找不到回文怎么辦?這個時候這個節點就單獨形成一個回文串,那么我們在判斷$s[i-len[x]-1]==s[i]$的時候,因為$len[1]=-1$,所以必定會停止,那么就不用擔心會無限跳下去了
然后來幾道題吧
這就是一個板子,順便記錄一下出現次數就好了
然后該有的注解都會寫在代碼里
1 //minamoto 2 #include<cstdio> 3 #include<cstring> 4 #define ll long long 5 template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;} 6 const int N=3e5+5; 7 char s[N]; 8 int n,p,q,fail[N],cnt[N],len[N],tot,last,ch[N][26]; 9 ll ans; 10 inline int newnode(int x){ 11 //建立一個新節點,長度為x 12 len[++tot]=x;return tot; 13 } 14 inline int getfail(int x,int n){ 15 //跳fail指針知道找到后綴回文為止 16 while(s[n-len[x]-1]!=s[n]) x=fail[x]; 17 return x; 18 } 19 int main(){ 20 scanf("%s",s+1); 21 //一堆亂七八糟的初始化 22 s[0]=-1,fail[0]=1,last=0; 23 len[0]=0,len[1]=-1,tot=1; 24 for(int i=1;s[i];++i){ 25 s[i]-='a'; 26 //找到可以回文的位置 27 p=getfail(last,i); 28 if(!ch[p][s[i]]){ 29 //如果有了轉移就不用建了,否則要新建 30 //前后都加上新字符,所以新回文串長度要加2 31 q=newnode(len[p]+2); 32 //因為fail指向的得是原串的嚴格后綴,所以要從p的fail開始找起 33 fail[q]=ch[getfail(fail[p],i)][s[i]]; 34 //記錄轉移 35 ch[p][s[i]]=q; 36 } 37 ++cnt[last=ch[p][s[i]]]; 38 } 39 for(int i=tot;i;--i) 40 cnt[fail[i]]+=cnt[i],cmax(ans,1ll*cnt[i]*len[i]); 41 printf("%lld\n",ans); 42 return 0; 43 }
我們肯定要先建出回文自動機的
然后如果是枚舉每一個節點暴跳fail指針肯定得T
那么我們對於每一個節點記錄一個$trans[i]$,表示小於等於它長度一半的節點
這個可以在建自動機的時候順便求出來,具體看代碼
然后對每一個節點判斷長度是否模4為0且$trans[i]$的長度是它的一半就好了
1 //minamoto 2 #include<cstdio> 3 #include<cstring> 4 template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;} 5 const int N=500005; 6 int fail[N],ch[N][26],cnt[N],len[N],trans[N]; 7 int n,m,tot,last,p,q,ans; 8 char s[N]; 9 inline int newnode(int x){ 10 len[++tot]=x;return tot; 11 } 12 inline int getfail(int x,int n){ 13 while(s[n-1-len[x]]!=s[n]) x=fail[x];return x; 14 } 15 void build(){ 16 for(int i=1;s[i];++i){ 17 int x=s[i]-'a'; 18 p=getfail(last,i); 19 if(!ch[p][x]){ 20 q=newnode(len[p]+2); 21 fail[q]=ch[getfail(fail[p],i)][x]; 22 ch[p][x]=q; 23 if(len[q]<=2) trans[q]=fail[q]; 24 else{ 25 int tmp=trans[p]; 26 while(s[i-1-len[tmp]]!=s[i]||(len[tmp]+2)*2>len[q]) tmp=fail[tmp]; 27 trans[q]=ch[tmp][x]; 28 } 29 } 30 cnt[last=ch[p][x]]++; 31 } 32 } 33 int main(){ 34 // freopen("testdata.in","r",stdin); 35 scanf("%d",&n); 36 scanf("%s",s+1); 37 s[0]=-1,fail[0]=1,last=0; 38 len[0]=0,len[1]=-1,tot=1; 39 build(); 40 for(int i=tot;i>=2;--i) cnt[fail[i]]+=cnt[i]; 41 for(int i=2;i<=tot;++i) 42 if((len[trans[i]]<<1)==len[i]&&len[i]%4==0) cmax(ans,len[i]); 43 printf("%d\n",ans); 44 return 0; 45 }
洛谷P4762 [CERC2014]Virus synthesis
先建一個回文自動機,然后記$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 #include<cstring> 3 #include<cstdio> 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 }
我感覺我整個人都自動機了……