回文自动机(PAM)总结

回文自动机是接受一个字符串的所有回文子串的自动机。
回文自动机中每个点代表原串的一个回文子串。
维护两种指针:(trans)(fail)
(x)(trans[x][c]) 指针指向在这个点代表回文串两端同时加字符 (c) 后得到的回文串。
(fail) 指针指向这个点所代表的回文串的一个最长的回文串后(前)缀。
构造:增量法。

void insert(int l,int r,int c)
{
	while(r-len[zr]-1<l||sz[r-len[zr]-1]!=c)
		zr=fa[zr];
	if(trs[zr][c])//存在
	{
		zr=trs[zr][c];
		return;//直接返回
	}
	int w=trs[zr][c]=++sl/*新建节点*/,t=fa[zr];
	while(t&&(trs[t][c]==0||r-len[t]-1<l||sz[r-len[t]-1]!=c))//寻找fail(注意)
		t=fa[t];
	if(trs[t][c])fa[w]=trs[t][c];
	else fa[w]=2;//指向偶根
	len[w]=len[zr]+2;zr=w;
}

细节:分为奇树,偶树,接受奇回文串,偶回文串。
设奇树根为1,偶树根为2。
将节点1的长度设为-1。节点2的长度设为0,fail设为1。
初始时的last设为1。
若没有fail指针,则fail指向2(偶根)。

int r0=2,r1=1;
fa[r0]=r1;zl=1;sl=2;
len[r0]=0;len[r1]=-1;

回文树的匹配类似AC自动机。

int w=1;//从1开始
for(int i=l;i<=r;i++)
{
	while(trs[w][sz[i]]==0||i-len[w]-1<l||sz[i-len[w]-1]!=sz[i])//注意
		w=fa[w];//走fail指针
	w=trs[w][sz[i]];
	qz[i]=w;
}

回文树具有性质:正反串的回文树是相同的。
因为每个节点都是回文,反转后不变,因此他们的trs,fail都是相同的。
这个性质,使得回文树可以支持在两端插入字符。
我们可以维护头尾两个 last 位置,然后在一个回文自动机中插入。
注意当整个串是一个回文串时需要把两个 last 都赋为这个回文串

if(len[zr]==r-l+1)zl=zr;

类似后缀自动机,回文自动机也可以定义right集合,求出回文串的出现次数。
将每次添加后的 last 设为红点。
在 fail 构成的树上,一个节点子树中红点个数就是它的出现次数。

这个也支持添加字符:
先离线读入所有添加字符操作,并记录每次的 last 。
对于每次操作,将last到根路径上都增加1即可。

在两端插入字符时,此法也是适用的。

定位子串可以倍增,具体方法类似后缀自动机:
先找到右端点代表的前缀,再按照长度倍增。

int getwzl(int l,int r)
{
	int cd=r-l+1,u=qz[r];
	if(len[u]<=cd)//注意此处
		return u;
	for(int i=17;i>=0;i--)
	{
		if(len[bz[i][u]]>cd)
			u=bz[i][u];
	}
	return fa[u];
}

定位到的是这个子串的最长回文后(前)缀。

原文地址:https://www.cnblogs.com/lnzwz/p/12261704.html