manacher+O(N)扫描。
简单说一下manacher吧,就是O(N)时间求出以每一个点为中心的回文串的长度,hdu上有一道裸题,绝对适合入门。
整体思路和kmp很像,都是通过之前的值推之后的值以保证O(N)的复杂度。
首先在每个字符之间插入'#'(或者其他不会出现的字符),第一个字符前和最后一个字符后都要插入。这样就避免了长度为偶数的回文串难以操作的问题。先在母串S就是操作后的字符串了,设p[i]是以i为中心向右最多拓展的距离(p[i]=1表示不拓展)。
维护mx,表示i之前最大的p[k]+k。设当前计算p[i],如果mx<p[i],直接暴力操作。否则,分情况讨论。否则就可以确定p[i]的下届。如图:
我们求出i关于k对称的2k-i,设为j,我们看到p[j]有两种情况。如绿线所示,如果p[j]在k的红线范围内。由于红线是一个回文串,所以2k-i对称到i的位置上还是一个回文串,所以p[i]=p[j],而且显然p[i]也不可能拓展了,否则p[j]也可以拓展。
还有一种情况,如蓝线所示,如果p[j]超过了红线的范围,那么p[i]显然有至少有mx-i的长度,至于能不能拓展呢?暴力即可。(我觉得是不能拓展了。单也说不准)
复杂度保证?显然所有暴力都会增加mx,所以暴力的次数最多O(N),所以复杂度O(N)
好了回到这道题目上来。我们还需要一个O(N)的扫描。设x[i]表示覆盖i的回文串的最左边的那个。即x[i]=最小的k满足p[k]+k-1>=i。y[]也是一样。然后枚举断开点更新答案就行了。
具体地细节详见代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#define inf 1000000000
#define N 500005
using namespace std;
int n,p[N],x[N],y[N];
char a[N],ch[N];
void work(){
int i,k=0;
for (i=1; i<=n; i++){
if (p[k]+k<i){
p[i]=0; while (a[i-p[i]]==a[i+p[i]]) p[i]++;
} else{
p[i]=min(p[k]+k-i,p[(k<<1)-i]);
while (a[i-p[i]]==a[i+p[i]]) p[i]++;
}
if (p[i]+i>p[k]+k) k=i;
}
}
int main(){
scanf("%s",ch+1);
a[n=1]='#'; int i,j,len=strlen(ch+1);
for (i=1; i<=len; i++){
a[++n]=ch[i]; a[++n]='#';
}
a[0]='$'; a[n+1]='%'; work();
for (i=1; i<=n; i++){ x[i]=inf; y[i]=-inf; }
int mx=1,mn=n;
for (i=1; i<=n; i++) if (p[i]>1){
if (i+p[i]>mx){
for (j=mx; j<i+p[i]; j++) x[j]=i;
mx=i+p[i];
}
}
for (i=n; i; i--) if (p[i]>1){
if (i-p[i]<mn){
for (j=mn; j>i-p[i]; j--) y[j]=i;
mn=i-p[i];
}
}
int ans=-inf;
for (i=1; i<=n; i++) if (a[i]=='#') ans=max(ans,y[i]-x[i]);
printf("%d\n",ans);
return 0;
}
2015.11.5
by lych