1. 程式人生 > >回文自動機(轉)

回文自動機(轉)

tdi div num strlen i++ -- 最長 nts 一個

轉載自此篇博客

回文自動機

類似AC自動機的一種回文串匹配自動機,也就是一棵字符樹。準確的說,是兩顆字符樹,0號表示回文串長度為偶數的樹,1號表示回文串長度為奇數的樹。每一個節點都代表一個字符串,並且類似AC自動機那樣,有字符基個兒子,它的第i個兒子就表示將字符基的第i個字符接到它表示的字符串兩邊形成的字符串。
同樣類似AC自動機的是,每一個節點都有一個fail指針,fail指針指向的點表示當前串後綴中的最長回文串。特殊地,0號點的fail指針指向1,非0、1號點並且後綴中不存在回文串的節點不指向它本身,而是指向0.

變量聲明:
len[i]:編號為i的節點表示的回文串的長度(一個節點表示一個回文串)。


ch[i][c]:編號為i的節點表示的回文串在兩邊添加字符c以後變成的回文串的編號(兒子)。
fail[i]:節點i失配以後跳轉不等於自身的節點i表示的回文串的最長後綴回文串。
cnt[i]:節點i表示的本質不同的串的個數(建樹時求出的不是完全的,最後重新統計一遍以後才是正確的)。
last:新添加一個字母後所形成的最長回文串表示的節點。
s[i]:第i次添加的字符(一開始設S[0] = -1,也可以是任意一個在串S中不會出現的字符)。
tot:添加的節點個數。
n:添加的字符個數。

下面是一個字符串abbaabba的回文自動機的建立過程。
技術分享圖片
技術分享圖片
技術分享圖片
技術分享圖片


技術分享圖片
技術分享圖片
技術分享圖片
技術分享圖片
技術分享圖片
技術分享圖片
技術分享圖片
技術分享圖片
技術分享圖片
技術分享圖片
技術分享圖片
技術分享圖片
技術分享圖片
這個建樹的過程看起來比較抽象,實際上,回文自動機的實質就是按順序添加字符,每添加一個字符都要找出以這個字符為後綴的最長的回文串,方法就是在以前一個為後綴的所有回文串中找到一個可以和這個點匹配的,然後再加入到樹中。
註意區分一下“字符”和“節點”的概念,一個字符就是字符串中的一個字符,而回文自動機當中的節點表示了一個字符串。

我們看一道板子題


【代碼實現】

 1 #include<cstdio>
 2 #include<cstring>
 3
#include<algorithm> 4 using namespace std; 5 const int N=3e5+5; 6 struct sd{ 7 int son[26],fail,len,num; 8 }t[N]; 9 int k,n,cnt=-1,id,last; 10 long long ans; 11 char st[N]; 12 void prepare() 13 { 14 t[++cnt].len=0; 15 t[++cnt].len=-1; 16 t[0].fail=1; 17 } 18 int get_fail(int v) 19 { 20 while(st[id-t[v].len-1]!=st[id]) v=t[v].fail; 21 return v; 22 } 23 void insert(char ch) 24 { 25 id++; 26 int v=ch-a; 27 int now=get_fail(last); 28 if(!t[now].son[v]) 29 { 30 t[now].son[v]=++cnt; 31 t[cnt].len=t[now].len+2; 32 t[cnt].fail=t[get_fail(t[now].fail)].son[v]; 33 if(t[cnt].fail==cnt) t[cnt].fail=0; 34 } 35 last=t[now].son[v]; 36 t[last].num++; 37 } 38 int main() 39 { 40 prepare(); 41 scanf("%s",st+1); 42 int k=strlen(st+1); 43 for(int i=1;i<=k;i++) 44 insert(st[i]); 45 for(int i=cnt;i>=1;i--) 46 t[t[i].fail].num+=t[i].num; 47 for(int i=1;i<=cnt;i++) 48 ans=max(ans,(long long)t[i].num*t[i].len); 49 printf("%lld",ans); 50 return 0; 51 } 52 /* 53 abacaba 54 */

回文自動機(轉)