bzoj2565 最长双回文串 manacher

    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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值