【洛谷P4287】双倍回文
题目
题目链接:https://www.luogu.com.cn/problem/P4287
记字符串\(w\)的倒置为\(w^R\)。例如\((abcd)^R=dcba\),\((abba)^R=abba\)。
对字符串x,如果\(x\)满足\(x^R=x\),则称之为回文;例如abba是一个回文,而abed不是。
如果x能够写成的\(ww^Rww^R\)形式,则称它是一个“双倍回文”。换句话说,若要\(x\)是双倍回文,它的长度必须是\(4\)的倍数,而且\(x\),\(x\)的前半部分,\(x\)的后半部分都要是回文。例如\(abbaabba\)是一个双倍回文,而\(abaaba\)不是,因为它的长度不是4的倍数。
\(x\)的子串是指在\(x\)中连续的一段字符所组成的字符串。例如\(be\)是\(abed\)的子串,而\(ac\)不是。
\(x\)的回文子串,就是指满足回文性质的\(x\)的子串。
\(x\)的双倍回文子串,就是指满足双倍回文性质的\(x\)的子串。
你的任务是,对于给定的字符串,计算它的最长双倍回文子串的长度。
\(n\leq 500000\)。
思路
不难发现,必然存在一个最长的双倍回文子串,其结束为止为 \(x\),且以 \(x\) 结尾的最长回文子串的长度等于最长双倍回文子串的长度。
假设不存在这样一个位置,那么显然最长双倍回文子串的长度要小于最长回文子串的长度。不妨设所有最长双倍回文子串中,结束位置最前的在 \(i\),以 \(i\) 结尾的最长回文子串的起始位置是 \(j\),以 \(i\) 结尾的最长双倍回文子串的起始位置是 \(k\),那么显然有 \(s_{i\sim i+j-k}=s_{k\sim j}\)。
所以对称过去必然还存在一个长度等于最长双倍回文子串长度的双倍回文子串,且其位置在 \(j\) 之前,矛盾。
所以我们可以在建 PAM 的同时维护出 \(f_x\) 表示以节点 \(x\) 结束且长度不超过 \(\lfloor\frac{\mathrm{len}_x}{2}\rfloor\) 的最长回文子串所属节点。如果其长度为偶数且恰好等于 \(\frac{\mathrm{len}_x}{2}\),那么就用 \(\mathrm{len}_x\) 更新答案。
时间复杂度 \(O(n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=500010;
int n,ans;
char s[N];
struct PAM
{
int tot,last,ch[N][26],len[N],fail[N],f[N];
PAM() { tot=1; fail[0]=1; len[1]=-1; }
int getfail(int n,int x)
{
while (s[n]!=s[n-len[x]-1]) x=fail[x];
return x;
}
void ins(int i)
{
int c=s[i]-'a',p=getfail(i,last);
if (!ch[p][c])
{
int np=++tot;
len[np]=len[p]+2;
f[np]=fail[np]=ch[getfail(i,fail[p])][c];
if (len[np]>2)
{
int q=f[p];
while (s[i]!=s[i-len[q]-1] || len[ch[q][c]]*2>len[np]) q=fail[q];
if (len[ch[q][c]]%2==0 && len[ch[q][c]]*2==len[np]) ans=max(ans,len[np]);
f[np]=ch[q][c];
}
ch[p][c]=np;
}
last=ch[p][c];
}
}pam;
int main()
{
scanf("%d%s",&n,s+1);
for (int i=1;i<=n;i++)
pam.ins(i);
printf("%d",ans);
return 0;
}