1. 程式人生 > 實用技巧 >迴文自動機 (PAM,Palindrome Automaton)

迴文自動機 (PAM,Palindrome Automaton)

迴文自動機 (PAM,Palindrome Automaton)

如果學習了\(\text{AC}\)自動機和字尾自動機(\(\text{SAM}\)),那麼這個冷門演算法其實非常簡單

約定:原字串為\(S\),長度為\(|S|\)

結構介紹

自動機節點意義: \(\text{PAM}\)沒有複雜的結構,每個節點對應了一種迴文子串,節點個數\(\leq |S|+2\)

自動機的轉移:\(\text{PAM}\)\(\text{AC}\)自動機一樣,有失配指標\(fail\)和匹配陣列\(nxt\)

\(fail_i\)即是\(i\)的字尾的最長狀態,\(i\)\(fail_i\)的邊構成了兩棵樹

,分別對應著長度為奇數/偶數的迴文子串

每個轉移\(nxt_{i,j}\)意味著在當前狀態\(i\)的串兩邊增加字元\(j\)

但是由於\(\text{PAM}\)的構造是一個線上演算法,所以如果想要像\(\text{AC}\)自動機一樣每次轉移直接訪問\(nxt\),需要結束後遍歷結構

構造

為了便於訪問,設偶數/奇數樹的根分別為\(0,1\),每個節點儲存一個當前狀態的長度\(len\),特別的,\(len_0=0,len_1=-1\),便於讓所有的串都滿足\(len_{nxt_{i,j}}=len_i+2\),同時偶數的根可以失配到奇數的根,即\(fail_0=1\),(實際這樣操作後就只有一棵樹了)

為了線上構造方便,\(\text{PAM}\)需要實現一個匹配函式\(\text{Find}(x,y)\),意思是在當前\(x\)狀態找到下一個位置\(S_y\)的匹配狀態,如果失配則返回奇數根\(1\)

int Find(int x,int y){
    while(s[y]!=s[y-len[x]-1]) x=fail[x]; // 如果失配到了x=1,那麼必然有s[y]=s[y]
    return x;
}

增加一個節點\(S_i=c\)

首先找到一個最長的匹配,設當前字首最長的迴文字尾對應的狀態為\(now\),則直接為\(now\)匹配\(S_i\)即可

然後是新建狀態(如果當前的迴文子串還未出現過)

\(\text{AC}\)自動機類似,訪問\(fail\)樹上最近的匹配即可得到這個點的\(fail\)

需要注意的點是,因為當前節點可以是根節點,尋找\(fail\)必須在新建轉移\(nxt_{now,c}\)之前進行,否則可能找到的\(fail\)是自己

void Extend(int i,int c){
    now=Find(now,i);
    if(!nxt[now][c]) {
        fail[++cnt]=nxt[Find(fail[now],i)][c];
		len[nxt[now][c]=cnt]=len[now]+2;
    }
    now=nxt[now][c];
}

模板程式碼如下:

char s[N];
struct Palindrome_Automaton{
	int len[N],fail[N],nxt[N][26],now,cnt;
	void Init(){ 
        rep(i,0,cnt) memset(nxt,fail[i]=0,104);
		s[now=0]=len[1]=-1;
		fail[0]=fail[1]=cnt=1;
	}
	int Find(int x,int y){ 
		while(s[y-len[x]-1]!=s[y]) x=fail[x];
		return x;
	}
	void Extend(int i,int c){
		now=Find(now,i);
    	if(!nxt[now][c]) {
        	fail[++cnt]=nxt[Find(fail[now],i)][c];
			len[nxt[now][c]=cnt]=len[now]+2;
    	}
    	now=nxt[now][c];
	}
};


拓展:迴文串與\(\text{Border}\)

關於\(\text{Border}\)

推論1:迴文串的\(\text{Border}\)也是迴文串

若有迴文串\(S\)的一個\(\text{Border} :T\),則\(S_{1,|T|}=S_{|S|-|T|+1,|S|}=reverse(S_{1,|T|})\)

\(T\)也是一個迴文串

推論2:遍歷迴文自動機的\(fail\)鏈,能得到當前串的所有\(\text{Border}\)(基於推論1得到)

結合\(\text{kmp,AC}\)\(\text{Border}\)的關係能夠有更好的理解