题目链接
http://main.edu.pl/en/archive/oi/19/pre
题目大意
给出一个长度为
n
的字符串,在串中找出一对长度为
思路
最终找到的一对合法前缀后缀一定是像这样的形式,分成两部分(红色段,绿色段),前缀里的红色段和后缀里的红色段相同,前缀里的绿色段和后缀里的绿色段相同。
我们可以枚举前缀里绿色段的最后一个字符下标
i
,假如我们能每次在
这里有个很有趣的结论:
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;
}