http://main.edu.pl/en/archive/oi/19/pre
给出一个长度为 n 的字符串,在串中找出一对长度为 L(2L≤n) 的前缀和后缀,使得这两个前缀和后缀是循环同构的,求 Lmax
最终找到的一对合法前缀后缀一定是像这样的形式,分成两部分(红色段,绿色段),前缀里的红色段和后缀里的红色段相同,前缀里的绿色段和后缀里的绿色段相同。
我们可以枚举前缀里绿色段的最后一个字符下标 i ,假如我们能每次在 O(1) 时间得到红色段的长度就好了,不妨设前缀里 [1,i−1] 部分是绿色段, [i,i+F[i]−1] 部分是红色段(红色段长度是 F[i] )。这样我们每次枚举完 i 后,用 i+F[i]−1 更新答案。
这里有个很有趣的结论: F[i−1]≤F[i]+2
可以用反证法证明。假设 F[i−1]>F[i]+2 ,画出下面的两个序列,分别代表了 F[i] 和 F[i−1] 的情况。
加入了蓝框里的字符后,红色区域变大了很多,我们把下面的 F[i−1] 情况的序列拆成左右两半,拼起来看,则除去了左右蓝框以外的中间的红色部分的长度就应该是 F[i] ,然而这里的 F[i] 比实际的 F[i] 大,矛盾了,证毕。
利用上述 F[] 存在单调性的结论,我们可以以 O(n) 时间快速求出所有的 F[] 。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXN 1100000
#define MAXP 1000000007
#define MOD 999911659
using namespace std;
typedef long long int LL;
int hash[MAXN],pow[MAXN],a[MAXN],f[MAXN],n;
char s[MAXN];
int gethash(int L,int R)
{
return (((LL)hash[R]-(LL)pow[R-L+1]*(LL)hash[L-1]%(LL)MOD)%MOD+MOD)%MOD;
}
int main()
{
int ans=0;
scanf("%d",&n);
pow[0]=1;
for(int i=1;i<=n;i++)
pow[i]=(LL)pow[i-1]*(LL)MAXP%(LL)MOD;
scanf("%s",s+1);
for(int i=1;i<=n;i++) a[i]=s[i];
for(int i=1;i<=n;i++)
hash[i]=((LL)hash[i-1]*(LL)MAXP+(LL)a[i])%(LL)MOD;
for(int i=n/2;i>=1;i--)
{
f[i]=min(f[i+1]+2,n/2-i+1);
while(f[i]>0&&gethash(i,i+f[i]-1)!=gethash(n-i+1-f[i]+1,n-i+1)) f[i]--;
}
for(int i=1;i<=n;i++) a[i]+=a[i-1];
if(f[1]) ans=max(ans,f[1]);
for(int i=2;i<=n/2;i++)
if(gethash(1,i-1)==gethash(n-i+2,n)&&a[i-1]-a[0]==a[n]-a[n-i+1])
ans=max(ans,i+f[i]-1);
printf("%d\n",ans);
return 0;
}