题目链接:SHOI2011]双倍回文 - 洛谷
前置知识: 回文自动机
有关回文自动机的介绍以及求法,这里推荐三篇博客
回文树 - OI Wiki
回文自动机学习笔记 - bztMinamoto - 博客园 (cnblogs.com)
Manacher 回文自动机 学习笔记_Clove_unique的博客-CSDN博客_回文自动机
这几个博客都讲解得很好,结合起来食用效果更佳!
如果觉得太抽象的话,可以在纸上画几个回文自动机,理解每个操作的原理。
题意
一个长度为$n$的字符串$S$,在$S$的子串中寻找形如$ss$($s$为回文串)的最长$ss$长度
思路
首先建立回文自动机,在求$fail$指针时,我们维护一个$go$指针,指向长度不大于当前回文串一半的最长回文后缀的节点。
我们考虑每次插入字符$c$的过程。
当我们新建一个节点$x$时,若该节点的父节点的长度已经小于等于该节点长度的一半,我们直接让$go_x $指向父节点,即$fail_x$。
否则我们从$x$的父节点的$go$指针不断向上跳,直到当前节点两端能拓展字符$c$并且拓展后长度不大于$x$的长度,那么$go_x$指向当前节点的儿子为$c$的子节点。
现在我们枚举回文自动机上的每个节点,若当前节点的长度为$4$的倍数且它的$go$指针指向的节点长度刚好为当前节点长度的一半,那么我们对该长度取$\max$,最后输出$\max$即可。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| #include<bits/stdc++.h> using namespace std; using LL = long long; const int N = 5e5 + 5, MOD = 998244353, INF = 0x3f3f3f3f;
struct Palindromic_Automaton { int s[N], top; int ch[N][26], fail[N], len[N], tot, last;
Palindromic_Automaton() { s[0] = -114514; tot = -1; New(0); New(-1); fail[0] = 1; last = 0; }
int New(int length) { len[++ tot] = length; return tot; }
int Get_Fail(int x) { while (s[top - len[x] - 1] != s[top]) { x = fail[x]; } return x; } int go[N]; void Extend(int c) { s[++ top] = c; int now = Get_Fail(last); if (!ch[now][c]) { int x = New(len[now] + 2); fail[x] = ch[Get_Fail(fail[now])][c]; ch[now][c] = x; if (len[fail[x]] <= len[x] / 2) { go[x] = fail[x]; } else { int y = go[now]; while (len[y] + 2 > len[x] / 2 || s[top - len[y] - 1] != s[top]) { y = fail[y]; } go[x] = ch[y][c]; } } last = ch[now][c]; } void solve() { int ans = 0; for (int i = 2; i <= tot; ++ i) { if (len[i] % 4 == 0 && len[i] == len[go[i]] * 2) { ans = max(ans, len[i]); } } cout << ans << '\n'; } } pam; signed main() { cin.tie(nullptr)->sync_with_stdio(false);
int n; string s; cin >> n >> s; for (const auto &x : s) { pam.Extend(x - 'a'); } pam.solve(); return 0; }
|