题目:https://www.luogu.org/problem/P4287
求最长双倍回文。
容易发现双倍回文的前半部分一定与整个回文在同一条fail链上,因此可以想出一种跳fail链统计答案的解法。
对于每个点,往上跳fail,直到跳到一个点,这个点的长度*2刚好是起始点的两倍,再判断一下起始点长度能否被4整除,都满足即可统计答案。
该解法如同各种自动机的连续跳fail做法一样,在fail树是一条链的时候复杂度退化到n方,需要用各种手法来优化。
首先可以想到用倍增来优化,这样复杂度可以变成O(nlogn),应该就可以通过这题了。
但是我在洛谷讨论区看到了一种非常有意思的解法,建一个“trans边”,即受你想要的要求限制的fail,在建树时顺便求出。
这是回文树找fail的代码:
int getFail(int x) {
while (S[n - len[x] - 1] != S[n]) {
x = fail[x];
}
return x;
}
那么这题要的trans只需要加一点点限制:
int getTrans(int x, int now) {
while (S[n - len[x] - 1] != S[n] || (len[x] + 2) * 2 > len[now]) {
x = fail[x];
}
return x;
}
就可以在O(n)的时间内愉快的找到想要的点。
这种思想非常有意思,这样可以针对不同的要求,连出不同的fail边。
ac代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 5;
int n;
char s[maxn];
struct Pam {
int next[maxn][26];
int fail[maxn], trans[maxn];
int len[maxn];
int S[maxn];
int last, n, p;
int newNode(int l) {
memset(next[p], 0, sizeof(next[p]));
len[p] = l;
return p++;
}
void init() {
n = last = p = 0;
newNode(0);
newNode(-1);
S[n] = -1;
fail[0] = 1;
}
int getFail(int x) {
while (S[n - len[x] - 1] != S[n]) {
x = fail[x];
}
return x;
}
int getTrans(int x, int now) {
while (S[n - len[x] - 1] != S[n] || (len[x] + 2) * 2 > len[now]) {
x = fail[x];
}
return x;
}
void add(int c) {
S[++n] = c;
int cur = getFail(last);
if (!next[cur][c]) {
int now = newNode(len[cur] + 2);
fail[now] = next[getFail(fail[cur])][c];
next[cur][c] = now;
if (len[now] <= 2) {
trans[now] = fail[now];
} else {
trans[now] = next[getTrans(trans[cur], now)][c];
}
}
last = next[cur][c];
}
void build() {
init();
for (int i = 0; s[i]; i++) {
add(s[i] - 'a');
}
}
void solve() {
int ans = INT_MIN;
for (int i = 2; i < p; ++i) {
if (len[trans[i]] * 2 == len[i] && len[i] % 4 == 0) {
ans = max(ans, len[i]);
}
}
printf("%d\n", ans);
}
} pam;
int main() {
scanf("%d%s", &n, s);
pam.build();
pam.solve();
return 0;
}