1. 程式人生 > >Palindromic Tree 回文自動機-回文樹 例題+講解

Palindromic Tree 回文自動機-回文樹 例題+講解

數組指針 post article 技術分享 ret tail 模板 分享 tree

---恢復內容開始---

回文樹,也叫回文自動機,是2014年被西伯利亞民族發明的,其功能如下:

1、求前綴字符串中的本質不同的回文串種類

2、求每個本質不同回文串的個數

3、以下標i為結尾的回文串個數/種類

4、每個本質不同回文串包含的本質不同回文串種類

(本文參考自Palindromic Tree——回文樹【處理一類回文串問題的強力工具】,Palindromic Tree 回文自動機-回文樹 解決回文串的神器)

下面介紹一些數組的意義

next[][]類似於字典樹,指向當前字符串在兩段同時加上一個字符

fail[] fail指針,類似於AC自動機,返回失配後與當前i結尾的最長回文串本質上不同的最長回文後綴

cnt[] 在最後統計後它可以表示形如以i為結尾的回文串中最長的那個串個數

num[] 表示以i結尾的回文串的種類數

len[] 表示以i為結尾的最長回文串長度

s[] 存放添加的字符

last 表示上一個添加的字符的位置

n 表示字符數組的第幾位

p 表示樹中節點的指針

以下內容轉自http://blog.csdn.net/u013368721/article/details/42100363

一開始回文樹有兩個節點,0表示偶數長度串的根和1表示奇數長度串的根,且len[0] = 0,len[1] = -1,last = 0,S[0] = -1,n = 0,p = 2(添加了節點0、1)。

技術分享圖片

技術分享圖片

假設現在我們有串S = abbaabba。

首先我們添加第一個字符‘a‘,S[++ n] = ‘a‘,然後判斷此時S[n - len[last] - 1]是否等於S[n],即上一個串-1的位置和新添加的位置是否相同,相同則說明構成回文。否則,last = fail[last]。此時last = 0,我們發現S[1 - 0 - 1] != S[1],所以last = fail[last] = 1,然後我們發現S[1 - (-1) - 1] == S[1](即自己等於自己,所以我們讓len[1]等於-1可以讓這一步更加方便)。

令cur等於此時的last(即cur = last = 1),判斷此時next[cur][‘a‘]是否已經有後繼,如果next[cur][‘a‘]沒有後繼,我們就進行如下的步驟:新建節點(節點數p++,且之後p = 3),並讓now等於新節點的編號(now = 2),則len[now] = len[cur] + 2(每一個回文串的長度總是在其最長子回文串的基礎上在兩邊加上兩個相同的字符構成的,所以是+2,同時體現出我們讓len[1] = -1的優勢,一個字符自成一個奇回文串時回文串的長度為(-1) + 2 = 1)。然後我們讓fail[now] = next[get_fail ( fail[cur] )][‘a‘],即得到fail[now](此時為fail[2] = 0),其中的get_fail函數就是讓找到第一個使得S[n - len[last] - 1] == S[n]的last。然後next[cur][‘a‘] = now。

當上面步驟完成後我們讓last = next[cur][c](不管next[cur][‘a‘]是否有後繼),然後cnt[last] ++。

此時回文樹為下圖狀態:

技術分享圖片

技術分享圖片

現在我們添加第二個字符字符‘b‘到回文樹中:

技術分享圖片

技術分享圖片

繼續添加第三個字符‘b‘到回文樹中:

技術分享圖片

技術分享圖片

繼續添加第四個字符‘a‘到回文樹中:

技術分享圖片

技術分享圖片

繼續添加第五個字符‘a‘到回文樹中:

技術分享圖片

技術分享圖片

繼續添加第六個字符‘b‘到回文樹中:

技術分享圖片

技術分享圖片

繼續添加第七個字符‘b‘到回文樹中:

技術分享圖片

技術分享圖片

繼續添加第八個字符‘a‘到回文樹中:

技術分享圖片

技術分享圖片

到此,串S已經完全插入到回文樹中了,現在所有的數據如下:

技術分享圖片

然後我們將節點x在fail指針樹中將自己的cnt累加給父親,從葉子開始倒著加,最後就能得到串S中出現的每一個本質不同回文串的個數。

構造回文樹需要的空間復雜度為O(N*字符集大小),時間復雜度為O(N*log(字符集大小)),這個時間復雜度比較神奇。如果空間需求太大,可以改成鄰接表的形式存儲,不過相應的要犧牲一些時間。

總的來說,這是一個很好的算法~

下面給出模板代碼

 1 const int MAXN = 100005 ;
 2 const int N = 26 ;
 3 
 4 struct Palindromic_Tree {
 5     int next[MAXN][N] ;//next指針,next指針和字典樹類似,指向的串為當前串兩端加上同一個字符構成
 6     int fail[MAXN] ;//fail指針,失配後跳轉到fail指針指向的節點
 7     int cnt[MAXN] ;
 8     int num[MAXN] ;
 9     int len[MAXN] ;//len[i]表示節點i表示的回文串的長度
10     int S[MAXN] ;//存放添加的字符
11     int last ;//指向上一個字符所在的節點,方便下一次add
12     int n ;//字符數組指針
13     int p ;//節點指針
14 
15     int newnode ( int l ) {//新建節點
16         for ( int i = 0 ; i < N ; ++ i ) next[p][i] = 0 ;
17         cnt[p] = 0 ;
18         num[p] = 0 ;
19         len[p] = l ;
20         return p ++ ;
21     }
22 
23     void init () {//初始化
24         p = 0 ;
25         newnode (  0 ) ;
26         newnode ( -1 ) ;
27         last = 0 ;
28         n = 0 ;
29         S[n] = -1 ;//開頭放一個字符集中沒有的字符,減少特判
30         fail[0] = 1 ;
31     }
32 
33     int get_fail ( int x ) {//和KMP一樣,失配後找一個盡量最長的
34         while ( S[n - len[x] - 1] != S[n] ) x = fail[x] ;
35         return x ;
36     }
37 
38     void add ( int c ) {
39         c -= a ;
40         S[++ n] = c ;
41         int cur = get_fail ( last ) ;//通過上一個回文串找這個回文串的匹配位置
42         if ( !next[cur][c] ) {//如果這個回文串沒有出現過,說明出現了一個新的本質不同的回文串
43             int now = newnode ( len[cur] + 2 ) ;//新建節點
44             fail[now] = next[get_fail ( fail[cur] )][c] ;//和AC自動機一樣建立fail指針,以便失配後跳轉
45             next[cur][c] = now ;
46             num[now] = num[fail[now]] + 1 ;
47         }
48         last = next[cur][c] ;
49         cnt[last] ++ ;
50     }
51 
52     void count () {
53         for ( int i = p - 1 ; i >= 0 ; -- i ) cnt[fail[i]] += cnt[i] ;
54         //父親累加兒子的cnt,因為如果fail[v]=u,則u一定是v的子回文串!
55     }
56 } ;

Palindromic Tree 回文自動機-回文樹 例題+講解