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
}