P4555

[国家集训队]最长双回文串

感觉很迷惑,尝试使用各种数据结构但发现并不是很好处理。。。

后来发现自己是个深必,怎么就想不到递推嘞。。。

Manacher 先求出 $d(i)$,容易想到以 $i$ 为中心的最长回文串长度为 $d(i)-1$,则我们设 $datl(i-d(i)+1)=\max {d(i)-1}$,$datr(i+d(i)-1)=\max{d(i)-1}$。

然后我们知道这个肯定没有完全求出 $datl(i)$ 和 $datr(i)$ 的值,因为这个回文串缩一缩也是可以的,所以我们想办法把这些短的回文串也给搞出来。

于是就有了数据结构递推,$datr(i)=\max{datr(i-2)-2}$,$datl(i)=\max{datl(i+2)-2}$。

一个正推一个反推,最后在除了第一个和最后一个 # 的位置取最大值即可。。。

时间复杂度 $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
56
57
58
59
60
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
namespace io {
inline ll read() {
ll ret=0,f=1;char ch=getchar();
while(ch<48||ch>57) {if(ch==45) f=-f;ch=getchar();}
while(ch>=48&&ch<=57) {ret=(ret<<3)+(ret<<1)+ch-48;ch=getchar();}
return ret*f;
}
inline void write(ll x) {
static char buf[22];static ll len=-1;
if(x>=0) {do{buf[++len]=x%10+48;x/=10;}while(x);}
else {putchar(45);do{buf[++len]=-(x%10)+48;x/=10;}while(x);}
while(len>=0) putchar(buf[len--]);
}
}
using io::read;using io::write;

const ll N=2e5;

ll d[N+5],datl[N+5],datr[N+5];
char a[N+5],s[N+5];

int main() {

scanf("%s",a);ll n=strlen(a);

s[0]='#';for(ll i=0;i<n;i++) {s[i*2+1]=a[i];s[i*2+2]='#';}
ll m=n*2+1;
for(ll i=0,l=0,r=-1;i<m;i++) {
ll k=(i>r)?1:min(d[l+r-i],r-i+1);
while(0<=i-k&&i+k<m&&s[i-k]==s[i+k]) k++;
d[i]=k--;if(i+k>r) {l=i-k;r=i+k;}
}

for(ll i=0;i<m;i++) {
datl[i+d[i]-1]=max(datl[i+d[i]-1],d[i]-1);
datr[i-d[i]+1]=max(datr[i-d[i]+1],d[i]-1);
}

ll ans=0;
for(ll i=0;i<m;i+=2) {
datr[i]=max(datr[i],datr[i-2]-2);
}
for(ll i=m-1;i>=0;i-=2) {
datl[i]=max(datl[i],datl[i+2]-2);
}
for(ll i=2;i<m-2;i+=2) {
// printf("s[%lld]=%c\n",i,s[i]);
ans=max(ans,datl[i]+datr[i]);
// printf("datl[%lld]=%lld datr[%lld]=%lld\n",i,datl[i],i,datr[i]);
}

write(ans);

return 0;
}