洛谷 4287 「SHOI2011」双倍回文

一个简单的想法是建出 PAM,枚举长度为 \(4\) 的倍数的状态,在 Fail 树上倍增求出其不超过长度一半的最长回文后缀长度,判断其是否恰等于其长度一半。

这样是 \(O(n \log n)\) 的,不够优美(雾
实际上这个不超过长度一半的最长回文后缀,可以用类似求 Fail 的方法,对于每个状态记录一个指针指向不超过其长度一半的最长回文后缀。
这样就 \(O(n)\) 了。

代码:

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
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5e5;
int n;
char s[N + 5];
int ans;
namespace PAM
{
struct node
{
int ch[26];
int fa,len;
int half;
} pam[N + 5];
int las = 1,tot = 1;
inline int init()
{
pam[1].len = -1,pam[0].fa = pam[0].half = 1;
return 0;
}
int Init = init();
void insert(char *s,int i)
{
int cur = las,x = s[i] - 'a';
for(;s[i - pam[cur].len - 1] ^ s[i];cur = pam[cur].fa);
if(!pam[cur].ch[x])
{
int p = ++tot,q = pam[cur].fa;
pam[p].len = pam[cur].len + 2;
for(;s[i - pam[q].len - 1] ^ s[i];q = pam[q].fa);
if(pam[p].len <= 2)
pam[p].half = pam[p].fa;
else
{
int half = pam[cur].half;
for(;(s[i - pam[half].len - 1] ^ s[i]) || (pam[half].len + 2 << 1) > pam[p].len;half = pam[half].fa);
pam[p].half = pam[half].ch[x];
}
pam[p].fa = pam[q].ch[x],pam[cur].ch[x] = p;
}
las = pam[cur].ch[x];
}
}
int main()
{
scanf("%d%s",&n,s + 1);
for(register int i = 1;i <= n;++i)
PAM::insert(s,i);
for(register int i = 2;i <= PAM::tot;++i)
if(!(PAM::pam[i].len & 3) && PAM::pam[PAM::pam[i].half].len == (PAM::pam[i].len >> 1))
ans = max(ans,PAM::pam[i].len);
printf("%d\n",ans);
}