[學習筆記]迴文自動機(PAM)
阿新 • • 發佈:2021-01-10
〇、測試連結
壹、定義
其實 \(\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
*/