Palindromic Tree 迴文自動機-迴文樹 解決迴文串的神器
迴文樹,也叫回文自動機,是2014年夏天戰鬥民族發明的,其功能如下:
1、求字首字串中的本質不同的迴文串種類
2、求每個本質不同迴文串的個數
3、以下標i為結尾的迴文串個數/種類
4、每個本質不同迴文串包含的本質不同迴文串種類
(本文參考自Palindromic Tree——迴文樹【處理一類迴文串問題的強力工具】)
在迴文樹中,每一個節點代表一個本質不同的迴文串,因為長度為n的字串最多有本質不同的迴文串n個,故迴文樹中至多有n節點,再加上兩個初始節點,至多有n+2個節點。
(以下程式碼和理解也是學習的鳥神的Palindromic Tree——迴文樹【處理一類迴文串問題的強力工具】,自己修改後的理解
首先我們定義一些變數。
1.len[i]表示編號為i的節點表示的迴文串的長度
2.next[i][c]表示編號為i的節點表示的迴文串在兩邊新增字元c以後變成的迴文串的編號(和Trie類似)。
3.fail[i]表示節點i失配以後跳轉不等於自身的節點i表示的迴文串的最長字尾迴文串(類似字尾自動機的parent指標)。
4.cnt[i]表示節點i表示的本質不同的串的個數(建樹時求出的不是完全的,最後count()函式按照拓撲序跑一遍以後才是正確的)
5.num[i]表示以節點i表示的最長迴文串的最右端點為迴文串結尾的迴文串個數,也可以用來在建樹過程中統計下標j的為結尾的迴文串個數。
6.last指向新新增一個字母后所形成的最長迴文串表示的節點。
7.S[i]表示第i次新增的字元(一開始設S[0] = -1(可以是任意一個在串S中不會出現的字元))。
8.p表示新增的節點個數。
9.n表示新增的字元個數。
那麼我們在建樹過程中,每新增一個字元c,至多會增加一個新的種類的迴文串(或是之前已經有的迴文串),由於形成的最長的迴文串一定是以字元c結尾,那麼這個迴文串必定是cTc形式,其中T為迴文串。想到這裡就可以使用last的fail指標可以遞迴找到一個最長的迴文字尾且前一個字元為c,否則此迴文串為c,可以看程式碼理解。
迴文樹最重要的是建樹過程,在建樹時num[last]即是以此迴文串的結尾為尾端的迴文串種類,也是以當前下標為結尾的迴文串的個數。
最後延拓撲序(類似字尾自動機,不過這裡的拓撲序就是是從p-1到0)更新cnt
下面給出程式碼:
const int maxn = 100005;// n(空間複雜度o(n*ALP)),實際開n即可 const int ALP = 26; struct PAM{ // 每個節點代表一個迴文串 int next[maxn][ALP]; // next指標,參照Trie樹 int fail[maxn]; // fail失配字尾連結 int cnt[maxn]; // 此迴文串出現個數 int num[maxn]; int len[maxn]; // 迴文串長度 int s[maxn]; // 存放新增的字元 int last; //指向上一個字元所在的節點,方便下一次add int n; // 已新增字元個數 int p; // 節點個數 int newnode(int w){ // 初始化節點,w=長度 for(int i=0;i<ALP;i++) next[p][i] = 0; cnt[p] = 0; num[p] = 0; len[p] = w; return p++; } void init(){ p = 0; newnode(0); newnode(-1); last = 0; n = 0; s[n] = -1; // 開頭放一個字符集中沒有的字元,減少特判 fail[0] = 1; } int get_fail(int x){ // 和KMP一樣,失配後找一個儘量最長的 while(s[n-len[x]-1] != s[n]) x = fail[x]; return x; } void add(int c){ c -= 'a'; s[++n] = c; int cur = get_fail(last); if(!next[cur][c]){ int now = newnode(len[cur]+2); fail[now] = next[get_fail(fail[cur])][c]; next[cur][c] = now; num[now] = num[fail[now]] + 1; } last = next[cur][c]; cnt[last]++; } void count(){ // 最後統計一遍每個節點出現個數 // 父親累加兒子的cnt,類似SAM中parent樹 // 滿足parent拓撲關係 for(int i=p-1;i>=0;i--) cnt[fail[i]] += cnt[i]; } }pam;
構造迴文樹需要的空間複雜度為O(N*字符集大小),時間複雜度為O(N*log(字符集大小)),這個時間複雜度比較神奇。如果空間需求太大,可以改成鄰接表的形式儲存,不過相應的要犧牲一些時間。
給出幾道練習題,程式碼也會在下一篇文章中給出。
1.ural1960. Palindromes and Super Abilities
2.TsinsenA1280. 最長雙迴文串
3.TsinsenA1255. 拉拉隊排練
4.TsinsenA1393. Palisection
5.2014-2015 ACM-ICPC, Asia Xian Regional Contest G The Problem to Slow Down You
6.bzoj 3676