1. 程式人生 > 實用技巧 >PAM學習小結

PAM學習小結

PAM

迴文自動機

建議先學習AC自動機:AC自動機講解超詳細

迴文自動機,顧名思義,用來處理迴文串的自動機。

功能:

1.求\(S\)串內本質不同的迴文串個數

2.求\(S\)串內本質不同的迴文串出現次數

3.最小回文劃分

4.\(S\)串中以下標\(i\)結尾的最長迴文串長度

迴文樹

看看自己感悟一下。感覺特別形象,都不用解釋了啊

還是稍微解釋一下:

1.迴文數上每一個節點代表了原串上出現過的一個本質不同迴文子串,原串上的每一個迴文子串都在迴文樹上有對應。迴文樹上每一個點代表的串都是迴文串。

2.迴文樹分兩部分,奇和偶,奇樹上的點代表的迴文串長度為奇數,偶樹上的為偶

3.兒子節點代表串長度為父親節點代表串長度\(+2\)

4.和\(Trie\)相似的其他性質,不說了

Fail指標

學過AC自動機的OIer們應該就很熟悉啦QwQ

\(Fail\)指標含義:這個節點所代表的迴文串的最長迴文字尾

Trans指標

一般做許多PAM題目常用的東西

\(Trans\)指標含義:小於等於當前節點長度一半最長迴文字尾

構建PAM

我們要維護以下資訊

char s[maxn];		//原串
int fail[maxn];		//fail指標
int len[maxn];		//該節點表示的字串長度
int tree[maxn][26];	//同Trie,指向兒子
int trans[maxn];	//trans指標
int tot,pre;		//tot代表節點數,pre代表上次插入字元後指向的迴文樹位置

其中\(fail,len,tree,trans\)為PAM上的資訊

構建PAM的方法為增量,即一個一個加入字元構建PAM

奇樹和偶樹的根長度\(len\)分別為\(-1\)\(0\)

設當前我們插入原串中\(i\)位置的字元\(u\)

那麼以\(i\)為結尾的最長迴文串應該為(以\(i-1\)為結尾的最長迴文串\(+u\)),並且那個迴文串要滿足前一個字元等於\(u\)(不然就不是迴文串了啊)

要找到那個點非常簡單,不斷從\(pre\)開始跳\(fail\),直到找到一個滿足\(s[i-len[x]-1]==u\) 的節點\(Fail\) ,那麼從\(Fail\)建一個\(u\)兒子即可以表示新的迴文串。

新點的\(fail\)怎麼求呢。

明顯為從\(pre\)開始跳\(fail\),找到{ [第二個(滿足\(s[i-len[x]-1]==u\)) 的節點\(x\) ]\(u\)兒子 }

也就是從\(Fail\)開始跳\(fail\),找到{ [第一個(滿足\(s[i-len[x]-1]==u\)) 的節點\(x\) ]\(u\)兒子 }

跳到根記得判斷

放程式碼理解:

int getfail(int x,int i){		//從x開始跳fail,滿足字元s[i]的節點
	while(i-len[x]-1<0||s[i-len[x]-1]!=s[i])x=fail[x];
	return x;
}
void insert(int u,int i){
	int Fail=getfail(pre,i);		//找到符合要求的點
	if(!tree[Fail][u]){		//沒建過就新建節點
		len[++tot]=len[Fail]+2;	//長度自然是父親長度+2
		fail[tot]=tree[getfail(fail[Fail],i)][u];	//fail為滿足條件的次短迴文串+u
		tree[Fail][u]=tot;		//認兒子
	}
	pre=tree[Fail][u];		//更新pre
}

至於\(trans\)維護也和\(fail\)差不多

根據\(trans\)的定義去推一下怎麼搞吧

放一下完整程式碼:

char s[maxn];		//原串
int fail[maxn];		//fail指標
int len[maxn];		//該節點表示的字串長度
int tree[maxn][26];	//同Trie,指向兒子
int trans[maxn];	//trans指標
int tot,pre;		//tot代表節點數,pre代表上次插入字元後指向的迴文樹位置
int getfail(int x,int i){		//從x開始跳fail,滿足字元s[i]的節點
	while(i-len[x]-1<0||s[i-len[x]-1]!=s[i])x=fail[x];
	return x;
}
int gettrans(int x,int i){
	while(((len[x]+2)<<1)>len[tot]||s[i-len[x]-1]!=s[i])x=fail[x];
	return x;
}
void insert(int u,int i){
	int Fail=getfail(pre,i);		//找到符合要求的點
	if(!tree[Fail][u]){		//沒建過就新建節點
		len[++tot]=len[Fail]+2;	//長度自然是父親長度+2
		fail[tot]=tree[getfail(fail[Fail],i)][u];	//fail為滿足條件的次短迴文串+u
		tree[Fail][u]=tot;		//指兒子
		if(len[tot]<=2)trans[tot]=fail[tot];	//特殊trans
		else{
			int Trans=gettrans(trans[Fail],i);	//求trans
			trans[tot]=tree[Trans][u];
		}
	}
	pre=tree[Fail][u];		//更新pre
}