所谓回文自动机,就是关于回文的自动机。
(逃)
前言
小清新自动机。
经历过SAM的大风大浪,这个相比而言好理解多了,感觉也许应该先学这个再学SAM…
解析
和trie、AC自动机、SAM等类似的,PAM的每个结点表示一个回文状态,每条边对应一条对应字符的转移边。定义每个结点的 fafafa 指针指向其最长的回文后缀。
和其他自动机有些不同的是,每条转移边表示的是在父亲的状态首尾各添加一个字符(这也是源于回文本身的性质)(奇根除外),所以每个结点的对应最长后缀长度为父亲长度 +2。
先给出算法流程:
考虑和SAM一样用增量法构造,假设目前已经建出了 [1,i−1][1,i-1][1,i−1] 的PAM,考虑加入 sis_isi。
设 si−1s_{i-1}si−1 对应的结点位置为 lstlstlst,那么我们就不断的从 lstlstlst 往上跳 fafafa,直到找到满足 si−lenp−1=sis_{i-len_{p}-1}=s_isi−lenp−1=si 的 ppp 结点。
此时,如果 ppp 结点存在 sis_isi 对应的转移,令 lst←transp,silst\gets trans_{p,s_i}lst←transp,si 即可。
否则,新建一个结点 curcurcur,令 lencur←lenp+2,transp,si←curlen_{cur}\gets len_{p}+2,trans_{p,s_i}\gets curlencur←lenp+2,transp,si←cur,接着再暴力往上跳 fafafa 寻找 facurfa_{cur}facur 即可。
回文串分为奇回文串和偶回文串两种,而且由于每次长度+2,奇偶性不变,这两种串在PAM上无法互相转化。因此,我们需要分别建一棵奇树和偶树,对应各自的奇根和偶根(分别标号为 1,01,01,0)。
细节处理上,需要令 len1=−1,len0=0,trans1/0,a...z=0,fa1=0,fa0=1len_{1}=-1,len_0=0,trans_{1/0,a...z}=0,fa_{1}=0,fa_0=1len1=−1,len0=0,trans1/0,a...z=0,fa1=0,fa0=1。
代码
int tot=1,lst=1;
struct node{int fa,len,num,tr[26];
}st[N];
void init(){st[0].len=0;st[0].fa=1;st[1].len=-1;st[1].fa=0;
}
int find(int x,int i){//printf(" find:x=%d i=%d ",x,i);while(s[i-st[x].len-1]!=s[i]){x=st[x].fa;} //printf("res=%d\n",x);return x;
}
void ins(int c,int id){c-='a';lst=find(lst,id);if(!st[lst].tr[c]){int cur=++tot;st[cur].fa=st[find(st[lst].fa,id)].tr[c];st[cur].len=st[lst].len+2;st[lst].tr[c]=cur;st[cur].num=st[st[cur].fa].num+1;}lst=st[lst].tr[c];return;
}
Thanks for reading!