把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

初学回文自动机

前言

回文自动机,据说是解决回文问题的利器。

最近刚好遇到一道回文问题,加上正好闲着没事干,就来学了学。

感觉板子还是非常简洁的,容易记忆。

节点信息

与一般自动机类似,定义一个节点的信息。

一般包括表示长度\(fail\)指针后继节点,当然视具体题目还要维护一些特殊信息。

但也要注意回文自动机与一般自动机的区别,就是它的后继\(x\)表示在当前串左右两边各加上一个字符\(x\)(要保证每个节点对应的串是回文串)。

奇根与偶根

考虑回文分为奇回文和偶回文,因此回文自动机一个特殊的地方就是,它有两个根:奇根以及偶根。

分别设置它们的初始信息:

节点编号 表示长度 \(fail\)指针
奇根 \(1\) \(-1\) 奇根偶根皆可
偶根 \(0\) \(0\) 奇根

\(fail\)指针

回文串和一般的字符串相比要相对复杂,因此它的\(fail\)指针也不能和其他自动机一样直接调用,而需要为它写一个专门的函数。

首先,一个节点的\(fail\)指针所指向的,是该回文串的最长回文后缀所对应的节点。

但在具体使用时,由于回文串的后继节点要求在串两侧各加上一个字符,因此一个节点的\(fail\)指针并不一定在任何时候都能实现扩展。

因此,我们需要不断跳\(fail\),直至找到一个合法的节点,满足它可以向两侧扩展。

这一过程可以定义为一个函数\(Fail(x,id)\),具体实现详见代码。

插入

插入一个新的位置\(id\),首先我们要通过\(t=Fail(lst,id)\)得到第一个可扩展位置。

如果\(t\)已经有对应儿子,就不需要操作了;否则,我们新建一个节点,节点长度就是\(t\)的长度加\(2\)(最后一次重复,回文串每次在串两侧各加上一个字符),而\(fail\)指针就是继续上跳,得到\(Fail(fail[t],id)\)

这一过程是非常简短,也非常简单的。

代码(板子题

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500000
using namespace std;
int n;char s[N+5];
class PalindromeAutomation
{
	private:
		int Nt,lst;struct node {int L,V,F,S[30];}O[N+5];
		I int Fail(RI x,CI id) {W(s[id-O[x].L-1]^s[id]) x=O[x].F;return x;}//根据实际情况找到合法的节点
	public:
		I PalindromeAutomation() {O[O[0].F=Nt=1].L=-1;}//初始化节点信息
		I int Ins(CI id)
		{
			RI x=s[id]&31,t=Fail(lst,id),o;!O[t].S[x]&&(o=++Nt,//如果没有对应儿子,新建节点
				O[o].L=O[t].L+2,O[o].V=O[O[o].F=O[Fail(O[t].F,id)].S[x]].V+1,O[t].S[x]=o);//计算当前点信息
			return O[lst=O[t].S[x]].V;//返回
		}
}P;
int main()
{
	RI i,lst=0;for(scanf("%s",s+1),n=strlen(s+1),i=1;i<=n;++i)
		s[i]=(s[i]-97+lst)%26+97,printf("%d%c",lst=P.Ins(i)," \n"[i==n]);return 0;
}
posted @ 2020-07-20 19:01  TheLostWeak  阅读(143)  评论(0编辑  收藏  举报