迴文樹或者回文自動機,及相關例題

迴文樹簡述

在大部分說法中,迴文樹與迴文自動機指的是一個東西;php

迴文樹是對一個字符串,基於自動機思想構建的處理迴文問題的樹形結構;算法

迴文樹是對着一個單串創建的;spa

因而他主要用於計數(迴文子串種類及個數)指針

基本創建思路是創建其前綴的迴文樹,而後每加上一個字符,統計產生了什麼迴文;code

迴文樹存在fail指針但通常不承接字符串匹配問題;blog

(迴文樹大概能夠斷定一個迴文串是否是一個串的子串,但KMP之類的能夠作得更好)字符串

構建好的迴文樹,是這樣的:get

(好難看)string

(點「偶」的fail指向點「奇」)it

可看出:

存在兩個樹結構,分別記錄奇數|偶數長度的迴文;

每一個點記錄一種字符串(可是因爲能夠經過根到該節點的路徑肯定這個字符串是什麼,因而並不須要真的在該節點記錄/*寫下*/這個信息)

每一個節點連字符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)的;(反正我也不會)

例題

BZOJ P2565 最長雙迴文串

對讀入的字符串,正反分別建迴文樹,在此同時分別記錄以每一個爲起點|終點的最長前|後綴迴文;

枚舉斷點,求最值便可;

 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 }

BZOJ P3676 [Apio2014]迴文串

對每一個點維護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來着,呵呵呵哈哈哈哈!!!!!!!!!!

相關文章
相關標籤/搜索