[学习笔记]回文自动机(PAM)
〇、测试链接
壹、定义
其实 \(\tt PAM\) 个人感觉是 \(\tt SAM\) 和 \(\tt AC\) 自动机的组合,用到的大部分思想来自于 \(\tt AC\) 自动机以及 \(\tt kmp\),但是采用的建立方式是 \(\tt SAM\) 的增量法,即来了一个点就在原来的基础上加入一个点.
对于一个 \(\tt PAM\) 的每个点,有这些是必不可少的东西:
- \(\tt son[26]\),表示在这个点所代表的字符串左右两端各加上字符后会到哪个点去;
- \(\tt fail\),类比 \(\tt AC\) 自动机,其含义是这个点所代表的串中的最长回文后缀(除自己);
- \(\tt len\),这个点代表的串的长度;
- \(\tt sz\),这个点所代表的串出现的次数;
同时,我们还有两个根,何为其然也?由于我们的 \(\tt son[]\) 的定义,从一个点到子点,都是加上两个点,但是显然,回文嘛,既有奇长度也有偶长度,故而就有奇根(\(0\))与偶根(\(1\)).
同时,由于使用增量法构建,我们还需记录上一个点的编号 \(\tt lst\).
贰、基础操作
我们假定 \(\tt s[n]\) 是一个新插入的点,下面给出如何寻找当前的最长回文后缀.
inline int getfail(int x){
while(s[n - len[x] - 1] != s[n]) x = fail[x];
return x;
}
可以类比 \(\tt kmp\) 的过程,不解释了.
然后就是加入一个新的字符
inline void add(const int x){
s[++ n] = x;
int cur = getfail(lst);
int now = son[cur][x];
if(!now){
now = ++ cnt;
len[now] = len[cur] + 2;
/** @brief if we start at cur, then we'll find itself
* because cur + x is the definition of point x
*/
fail[now] = son[getfail(fail[cur])][x];
son[cur][x] = now;
ans[now] = ans[fail[now]] + 1;
}
++ sz[now], lst = now;
}
我们首先找到最长的、在加上 \(\tt s[n]\) 之后仍然可以保持回文的后缀 \(\tt cur\),那么我们的新点就是 \(\tt cur\) 的 \(\tt son[s[n]]\),但是我们要判断一下 \(\tt cur\) 是否存在 \(\tt son[s[n]]\),如果不存在,那么我们新加点,加点时有几个注意事项:
- \(\tt fail[now]=son[getfail(fail[cur])][x]\) 一句中,必须从 \(\tt fail[cur]\) 开始,不然找到的就是 \(\tt now\) 自己;
- \(\tt son[cur][x]=now\) 必须放在 \(\tt fail[now]=son[getfail(fail[cur])][x]\) 之后,因为更改了 \(\tt son[cur]\) 之后,可能会对这一句有影响,比如当 \(\tt cur=1\) 的特殊情况.
还要注意的是,如果你的字符的哈希值从 \(0\) 开始,那么要将 \(\tt s[0]\) 赋值为字符哈希值以外的值,否则会因为 \(\tt 'a'=0\) 同时空字符亦为 \(0\) 而 \(\tt WA\) 掉.
叁、代码
# include <bits/stdc++.h>
using namespace std;
namespace Elaina{
# define rep(i,l,r) for(int i=l, i##_end_ = r; i <= i##_end_; ++ i)
# define fep(i,l,r) for(int i=l, i##_end_ = r; i >= i##_end_; -- i)
# define fi first
# define se second
# define Endl putchar('\n')
# define writc(x, c) fwrit(x), putchar(c)
// # define int long long
typedef long long ll;
typedef pair<int, int> pii;
typedef unsigned long long ull;
typedef unsigned int uint;
template<class T>inline T Max(const T x, const T y){return x < y ? y : x;}
template<class T>inline T Min(const T x, const T y){return x < y ? x : y;}
template<class T>inline T fab(const T x){return x < 0 ? -x : x;}
template<class T>inline void getMax(T& x, const T y){x = Max(x, y);}
template<class T>inline void getMin(T& x, const T y){x = Min(x, y);}
template<class T>T gcd(const T x, const T y){return y ? gcd(y, x % y) : x;}
template<class T>inline T readin(T x){
x=0; int f = 0; char c;
while((c = getchar()) < '0' || '9' < c) if(c == '-') f = 1;
for(x = (c ^ 48); '0' <= (c = getchar()) && c <= '9'; x = (x << 1) + (x << 3) + (c ^ 48));
return f ? -x : x;
}
template<class T>void fwrit(const T x){
if(x < 0)return putchar('-'), fwrit(-x);
if(x > 9)fwrit(x / 10); putchar(x % 10 ^ 48);
}
}
using namespace Elaina;
const int maxn = 5e5;
char str[maxn + 5]; int lenth;
namespace PAM{
/** @brief the string*/
int s[maxn + 5], n;
/** @brief the answer of each point*/
int ans[maxn + 5];
/** @brief the son of each node*/
int son[maxn + 5][26];
/** @brief means the longest palindrome of a node(except itself)*/
int fail[maxn + 5];
/** @brief the length of a node*/
int len[maxn + 5];
/** @brief the number of appearance*/
int sz[maxn + 5];
/** @brief the lst node to be insert*/
int lst;
/** @brief the count of nodes*/
int cnt;
inline int getfail(int x){
while(s[n - len[x] - 1] != s[n]) x = fail[x];
return x;
}
inline void add(const int x){
s[++ n] = x;
int cur = getfail(lst);
int now = son[cur][x];
if(!now){
now = ++ cnt;
len[now] = len[cur] + 2;
/** @brief if we start at cur, then we'll find itself
* because cur + x is the definition of point x
*/
fail[now] = son[getfail(fail[cur])][x];
son[cur][x] = now;
ans[now] = ans[fail[now]] + 1;
}
++ sz[now], lst = now;
}
inline void build(){
cnt = lst = 1;
len[1] = -1, fail[0] = fail[1] = 1;
s[0] = -1; // the most important, because the hash id is stared at 0, so the empty node should be different from the hash num
add(str[1] - 'a');
printf("%d", ans[lst]);
rep(i, 2, lenth){
add((ans[lst] - 97 + str[i]) % 26 + 97 - 'a');
printf(" %d", ans[lst]);
}
}
}
using namespace PAM;
inline void init(){
scanf("%s", str + 1);
lenth = strlen(str + 1);
}
signed main(){
init();
build();// pay attention !!!
return 0;
}
/*
azyx (aaaa)
1 2 3 4
*/