回文樹簡述
在大部分說法中,回文樹與回文自動機指的是一個東西;
回文樹是對一個字符串,基於自動機思想構建的處理回文問題的樹形結構;
回文樹是對着一個單串建立的;
於是他主要用於計數(回文子串種類及個數)
基本建立思路是建立其前綴的回文樹,然后每加上一個字符,統計產生了什么回文;
回文樹存在fail指針但一般不承接字符串匹配問題;
(回文樹大概可以判定一個回文串是不是一個串的子串,但KMP之類的可以做得更好)
構建好的回文樹,是這樣的:

(好難看)
(點“偶”的fail指向點“奇”)
可看出:
存在兩個樹結構,分別記錄奇數|偶數長度的回文;
每個點記錄一種字符串(但是由於可以通過根到該節點的路徑確定這個字符串是什么,於是並不需要真的在該節點記錄/*寫下*/這個信息)
每個節點連字符x邊向一個子節點,表示在她的左右兩邊加x構成的回文是這個總字符串的子串(根節點相關部分單獨定義);
每個節點連一條fail指針向其最長后綴回文;
幾個性質
|s|表示s串的長度;
節點數不超過|s|:每個節點是互不相同的回文,每個回文都是某一個點的最長后綴回文,而每個點的最長后綴回文是唯一的;
轉移數是O(|s|)級的:樹有節點數-2個邊(因為是兩棵樹),加上每個節點一個fail,於是是O(|s|)級的;
構造方法
回文樹的基礎插入算法:
建立s的回文樹;
現在已建立了s的前綴x,然后考慮插入下一個字符;
每次插入最多只會把她的最長后綴回文貢獻到樹中;
證明:
c除最長后綴回文之外的后綴回文i是其最長后綴回文k的子串,則她關於k的中心有一個對稱串j,由於k本身是對稱的,於是j與i本質相同,由於j已經加入回文樹中,於是i不必加入;
那么我們設x串之前的最長后綴回文是t,在加入c后,我們期望的c的最長后綴回文最長是ctc,
但t串前面不是字符c怎么辦,
試試t的除自己外最長回文后綴的前一個字符是不是c,
若不行,再試試她的最長回文后綴。。。。
這樣找的的回文如果還沒存在與回文樹中,則將其插入;
為了方便這一過程,我們維護每點一個指針fail,s當前的前綴x的最長回文后綴t,沒有ctc,就跳fail,直到可以匹配;
建fail的過程與匹配過程類似;
然后維護一個last指向當前在的節點,以便插入新的回文;
通過勢能分析法發現這個算法是每次插入均攤O(1),總共O(n)的;(反正我也不會)
例題
對讀入的字符串,正反分別建回文樹,在此同時分別記錄以每個為起點|終點的最長前|后綴回文;
枚舉斷點,求最值即可;
1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 struct dt{ 5 int ch[30],fail,len; 6 }; 7 struct Pld_T{ 8 int tot,last; 9 dt data[500500]; 10 int len[500500]; 11 char s[500500]; 12 void Init(){ 13 data[0].fail=1; 14 data[1].len=-1; 15 tot=1;last=0; 16 } 17 void insert(int x){ 18 int j; 19 while(s[x-data[last].len-1]!=s[x])last=data[last].fail; 20 if(!data[last].ch[s[x]-'a']){ 21 data[++tot].len=data[last].len+2; 22 j=data[last].fail; 23 while(s[x-data[j].len-1]!=s[x])j=data[j].fail; 24 data[tot].fail=data[j].ch[s[x]-'a']; 25 data[last].ch[s[x]-'a']=tot; 26 last=tot; 27 } 28 else 29 last=data[last].ch[s[x]-'a']; 30 len[x]=data[last].len; 31 } 32 }; 33 Pld_T pld_t1,pld_t2; 34 int main() 35 { 36 int i,j,k,len,ans=0; 37 pld_t1.Init();pld_t2.Init(); 38 scanf("%s",pld_t1.s+1); 39 len=strlen(pld_t1.s+1); 40 for(i=len,j=1;i>=1;i--,j++) 41 pld_t2.s[j]=pld_t1.s[i]; 42 pld_t2.s[0]=pld_t1.s[0]='#'; 43 pld_t2.s[len+1]=pld_t1.s[len+1]='\0'; 44 for(i=1;i<=len;i++){ 45 pld_t1.insert(i); 46 pld_t2.insert(i); 47 } 48 for(i=1;i<len;i++) 49 if(ans<pld_t1.len[i]+pld_t2.len[len-i]) 50 ans=pld_t1.len[i]+pld_t2.len[len-i]; 51 printf("%d",ans); 52 return 0; 53 }
對每個點維護cnt表示她是幾個字符的最長回文后綴;
那么他出現的次數就是她作為自己的末端字符的最長回文后綴出現的次數,以及她作為自己的末端字符的最長回文后綴的回文后綴(也不比是除她外最長的)出現的次數;
見代碼
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 char s[300010]; 6 struct Pld_T{ 7 int ch[26],fail,len,cnt; 8 }; 9 Pld_T pld_t[300010]; 10 int tot; 11 int main() 12 { 13 int i,j,k,len; 14 long long ans=0; 15 scanf("%s",s+1); 16 len=strlen(s+1);s[0]='#'; 17 pld_t[0].fail=1;k=0;pld_t[1].len=-1;tot=1; 18 for(i=1;i<=len;i++){ 19 while(s[i-pld_t[k].len-1]!=s[i])k=pld_t[k].fail; 20 if(!pld_t[k].ch[s[i]-'a']){ 21 pld_t[++tot].len=pld_t[k].len+2; 22 j=pld_t[k].fail; 23 while(s[i-pld_t[j].len-1]!=s[i])j=pld_t[j].fail; 24 pld_t[tot].fail=pld_t[j].ch[s[i]-'a']; 25 pld_t[k].ch[s[i]-'a']=tot; 26 } 27 k=pld_t[k].ch[s[i]-'a']; 28 pld_t[k].cnt++; 29 } 30 for(i=tot;i>=2;i--){ 31 pld_t[pld_t[i].fail].cnt+=pld_t[i].cnt; 32 if((long long)pld_t[i].cnt*pld_t[i].len>ans) 33 ans=(long long)pld_t[i].cnt*pld_t[i].len; 34 } 35 printf("%lld",ans); 36 return 0; 37 }
我之前想在fail樹上建LCT來着,呵呵呵哈哈哈哈!!!!!!!!!!
