初学回文自动机
前言
回文自动机,据说是解决回文问题的利器。
最近刚好遇到一道回文问题,加上正好闲着没事干,就来学了学。
感觉板子还是非常简洁的,容易记忆。
节点信息
与一般自动机类似,定义一个节点的信息。
一般包括表示长度、\(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;
}
待到再迷茫时回头望,所有脚印会发出光芒