想不到吧我还在更新哈哈哈哈哈。
今天带来一个2014年发明的算法——回文自动机。
既然写了这篇博客,那我就说得全面一些。
回文自动机
这个算法也称回文树。
可以类比一些其他的自动机,回文自动机也是有许多节点、许多next、许多fail的。
1、节点的那些事
每个节点都表示一个回文子串(注意了啊,只表示一个,SAM才是一类),且任意两个节点所表示的串必定不全等。(可以用数归证明,一个长度为n的串,其本质不同的回文子串数在O(n)级别)
2、数组定义
next[i][c]
n
e
x
t
[
i
]
[
c
]
表示在i节点所代表的串的两端各添加一个字符c,得到的那个新回文子串所在的节点。
fail[i]
f
a
i
l
[
i
]
表示i节点的串的最长回文后缀。
cnt[i]
c
n
t
[
i
]
表示这个串共出现了几次。
len[i]
l
e
n
[
i
]
表示节点i这个串的长度。
num[i]
n
u
m
[
i
]
表示这个串有多少回文后缀。
int nxt[N][26]; //i节点对应的回文串在两边各加一个字符后变成的节点编号
int fail[N]; //fail[i]表示的串是i的最长后缀回文串
int cnt[N]; //表示这个串出现过几次
int num[N]; //节点i这个串的后缀有多少是回文的
int len[N]; //节点i表示的回文串的长度
int S[N]; //S[i]是第i次添加的字符
int last; //以n结束的最长回文串所在的节点
int n; //目前添加的字符个数
int p; //下一个新建节点的标号
3、回文串长度奇偶处理
在初始时,0节点赋0的长度,1节点赋-1的长度。last指向0,0的fail指向1。同时Str[0] = -1。
具体原因不讲。
inline int newnode(int l) {
//新建节点
for(int i = 0; i < 26; ++i) nxt[p][i] = 0;
cnt[p] = num[p] = 0;
len[p] = l;
return p++;
}
inline void init() {
p = 0;
newnode(0);
newnode(-1);
last = n = 0;
S[n] = -1;
fail[0] = 1;
}
4、判断合法
新加进来一个字符时,只有
Str[Nnow−len[x]−1]=Str[Nnow]
S
t
r
[
N
n
o
w
−
l
e
n
[
x
]
−
1
]
=
S
t
r
[
N
n
o
w
]
我们才可以说是能构成一个新的回文串(构成回文节点唯一方法,就是在已有基础上找一个节点,在其两端各拓展一个字符,也就是我们所说的找到一个x,使式子成立)。
于是我们就可以去不停给last跑fail,直到满足条件。
inline int get_fail(int x) {
while(S[n - len[x] - 1] != S[n]) x = fail[x];
return x;
}
5、加一个字符进来
inline void add(int c) {
S[++n] = c;
int cur = get_fail(last); //通过上一个回文串找这个串的匹配位置
if(!nxt[cur][c]) {
//这个回文串从未出现过
int now = newnode(len[cur] + 2);
fail[now] = nxt[get_fail(fail[cur])][c]; //和AC自动机简直不要太类似
nxt[cur][c] = now;
num[now] = num[fail[now]] + 1;
}
last = nxt[cur][c];
++cnt[last];
}
5、把cnt搞对
inline void dp() {
for(int i = p - 1; i >= 0; --i) cnt[fail[i]]+= cnt[i];
}
加完所有串之后,dp一下才对。
这个东西极妙,最好背一下板子。
贴一个 【APIO2014】 【 A P I O 2014 】 回文串的 code c o d e :
#include <cstdio>
#define N 300010
inline long long mymax(long long x, long long y) {return x > y ? x : y;}
struct Palindromic_Tree {
int nxt[N][26]; //i节点对应的回文串在两边各加一个字符后变成的节点编号
int fail[N]; //fail[i]表示的串是i的最长后缀回文串
int cnt[N]; //表示这个串出现过几次
int num[N]; //节点i这个串的后缀有多少是回文的
int len[N]; //节点i表示的回文串的长度
int S[N]; //S[i]是第i次添加的字符
int last; //以n结束的最长回文串所在的节点
int n; //目前添加的字符个数
int p; //下一个新建节点的标号
inline int newnode(int l) {
//新建节点
for(int i = 0; i < 26; ++i) nxt[p][i] = 0;
cnt[p] = num[p] = 0;
len[p] = l;
return p++;
}
inline void init() {
p = 0;
newnode(0);
newnode(-1);
last = n = 0;
S[n] = -1;
fail[0] = 1;
}
inline int get_fail(int x) {
while(S[n - len[x] - 1] != S[n]) x = fail[x];
return x;
}
inline void add(int c) {
S[++n] = c;
int cur = get_fail(last); //通过上一个回文串找这个串的匹配位置
if(!nxt[cur][c]) {
//这个回文串从未出现过
int now = newnode(len[cur] + 2);
fail[now] = nxt[get_fail(fail[cur])][c]; //和AC自动机简直不要太类似
nxt[cur][c] = now;
num[now] = num[fail[now]] + 1;
}
last = nxt[cur][c];
++cnt[last];
}
inline void dp() {
for(int i = p - 1; i >= 0; --i) cnt[fail[i]]+= cnt[i];
}
inline void work() {
init();
char c = getchar();
while(c >= 'a' && c <= 'z') {
add(c - 'a');
c = getchar();
}
dp(); long long ans = 0;
for(int i = p - 1; i >= 0; --i) ans = mymax(ans, 1ll * cnt[i] * len[i]);
printf("%lld", ans);
}
}pdtree;
int main() {
pdtree.work();
return 0;
}