用途
用于解决一些关于回文的问题,处理回文问题的利器。
定义
有2个根,odd和even。
even的长度是0,odd的长度是-1,什么意思呢?
就是比如说插入一个字符’a’,插入even时会变成‘aa’,而插入odd时会变成’a’。
所以odd代表长度为奇数的回文串,而even代表长度为偶数的回文串。
len为一个点所代表的字符串实际长度。
fail为失配指针,指向最长回文后缀。
构建
考虑我们加入一个字符c,假设上一个字符串为 S l . . r S_{l..r} Sl..r,若 S l − 1 S_{l-1} Sl−1等于新加入的 c c c,则直接加入即可。
若 S l − 1 S_{l-1} Sl−1不等于 c c c,则我们需要在 S l . . r − 1 S_{l..r-1} Sl..r−1找到一个最长的回文后缀,使 S l ′ . . r S_{l'..r} Sl′..r为回文串。
所以fail指针指向的就是这个点所代表的字符串的最长回文后缀所在的节点。
如babcbab指向bab。
一些结论
1.对于任意一个串S,它的本质不同的回文串的个数不超过 l e n ( S ) len(S) len(S)
2.在串S后面加入一个字符,新增的本质不同的回文串的个数不超过1个
证明先不写了,数学归纳法。
在插入一个字符串时,我们首先看它新增的回文串是否存在,如存在,则直接跳到相应节点更新信息。
否则新开一个节点来代表这个字符串即可。
新开一个节点后还需要更新这个节点的所有信息即可。
详细细节见代码,例题APIO2014【回文串】
#include <cstdio>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
const int N = 3e5 + 5;
int n;
char s[N];
ll ans;
struct Palindrome_Automaton
{
int fail[N],to[N][26],cnt[N],len[N],tot,Len,last;
void First()
{
tot = 1;
fail[0] = 1,len[1] = -1; // 0代表ven,1代表odd
last = 0;
s[0] = -1;
}
int Get_Fail(int p) // 依fail链跳转
{
while (s[Len - len[p] - 1] != s[Len]) p = fail[p];
return p;
}
void add(int c)
{
Len++;
int cur = Get_Fail(last); // 找到满足条件的地方
if (!to[cur][c])
{
int p = ++tot;
len[p] = len[cur] + 2;
fail[p] = to[Get_Fail(fail[cur])][c]; // 注意这里里面不是cur,是fail[cur],因为如果是cur的话就无法更新了。
to[cur][c] = p;
}
last = to[cur][c];
cnt[last]++;
}
void sum()
{
for (int i = tot ; i ; i--)
{
cnt[fail[i]] += cnt[i];
ans = max(ans,1LL * len[i] * cnt[i]);
}
}
}P_Tree;
int main()
{
freopen("palindrome.in","r",stdin);
freopen("palindrome.out","w",stdout);
P_Tree.First();
scanf("%s",s + 1);
n = strlen(s + 1);
for (int i = 1 ; i <= n ; i++) P_Tree.add(s[i] - 'a');
P_Tree.sum();
printf("%lld\n",ans);
return 0;
}
复杂度分析
时间复杂度是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)的,用到了势能分析,不会。
空间复杂度是 O ( n ∗ s t r ( s ) ) O(n*str(s)) O(n∗str(s))的, s t r ( s ) str(s) str(s)是字符集大小。