迴文自動機學習筆記

前言html

剛學完manacher就來學回文自動機……node

感受好像(板子)也不是很難(背)ui

前置知識:Manacher(也不必定非要由於和這個沒啥關係),知道自動機是個啥以及怎麼建spa

簡述.net

迴文樹和迴文自動機指的是同一個東西指針

是由某西伯利亞人於2014夏發明的code

這東西主要是用於計數,計算迴文串的個數以及種類啥的htm

建樹blog

圖我就不放了(太亂了放了也看不懂),要看圖的話能夠去這位大神的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$,因此一定會中止,那麼就不用擔憂會無限跳下去了

而後來幾道題吧

洛谷P3649 [APIO2014]迴文串

這就是一個板子,順便記錄一下出現次數就行了

而後該有的註解都會寫在代碼裏

 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 }

洛谷P4287 [SHOI2011]雙倍迴文

咱們確定要先建出迴文自動機的

而後若是是枚舉每個節點暴跳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 }

 我感受我整我的都自動機了……

相關文章
相關標籤/搜索