参考资料:
翁文涛在2017年国家候选队的论文。
前言:
感觉这个东西比后缀自动机好理解。
博主是看wwt的论文学的:
wwt给出的这种不用打通配符的方法更加简洁。
定义:
回文树有两个根。
分别为even,odd,长度分别是0和-1。
len为一个点代表的字符串的实际长度。
fail为这个点失配后的最长回文后缀。
go是自动机的边。
构建:
fail[even]=odd
用和后缀自动机一样的增量法。
考虑当前的串是t,在t后面加一个x,为tx。
若t的fail链上有一p,p是回文串。
如果有s[n-|p|-1]=s[n],那么xpx就是合法的回文后缀了。
于是找到最大的满足条件的p就好了。
如果不存在这个xpx这个串,显然需要把它加到树里,并且维护一下它的fail。
复杂度分析:
势能分析,博主不会。
时间复杂度O(n log n),空间复杂度O(n *|str|),str为可能的字符集大小。
例题:
【APIO2014】回文串
这题有很多解法,最简单的一种就是直接上回文树。
张天杨的论文里也提到了这题,可以建后缀自动机,然后反串丢上去匹配。
但是好像当时的标算是用manacher求回文串,后缀数组(后缀自动机)求出现次数。
Code:
#include<cstdio>
#include<cstring>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
#define ll long long
#define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;
const int N = 3e5 + 5;
char s[N]; ll ans;
struct hwt {
int fail[N], to[N][26], cnt[N], len[N], tot, last, n;
void First() {
tot = 1;
len[1] = -1;
fail[0] = 1;
last = 0;
s[0] = -1;
}
int Getfail(int p) {
while(s[n - len[p] - 1] != s[n]) p = fail[p];
return p;
}
void add(char c) {
n ++;
int cur = Getfail(last);
if(!to[cur][c]) {
int p = ++ tot;
len[p] = len[cur] + 2;
fail[p] = to[Getfail(fail[cur])][c];
to[cur][c] = p;
}
last = to[cur][c];
cnt[last] ++;
}
void Sum() {
fd(i, tot, 1) {
cnt[fail[i]] += cnt[i];
ans = max(ans, (ll)cnt[i] * len[i]);
}
}
} str;
int main() {
freopen("palindrome.in","r",stdin);
freopen("palindrome.out","w",stdout);
str.First();
scanf("%s", s + 1); int l = strlen(s + 1);
fo(i, 1, l) str.add(s[i] - 'a');
str.Sum();
printf("%lld", ans);
}