BZOJ2565:最长双回文串——题解

http://www.lydsy.com/JudgeOnline/problem.php?id=2565

题目大意:

顺序和逆序读起来完全一样的串叫做回文串。比如acbca是回文串,而abc不是(abc的顺序为“abc”,逆序为“cba”,不相同)。
输入长度为n的串S,求S的最长双回文子串T,即可将T分为两部分XY,(|X|,|Y|≥1)且XY都是回文串。
 
——————————————————————
看到回文串长度最大,先敲一个manacher算法。
然后思考如何更新每一个字符以其为起点和终点的最长回文串。
显然都跑一遍肯定是不行的(尝试过TLE…)
那么我们考虑一个很简单的事实,对于一个在某几个回文串的字符,显然当它属于mx靠前的回文串时以它为终点的回文串长度最长。
那么我们就有了一种近似O(n)的算法,通过记录我们最新一次更新的字符位置now,然后搜索每一个回文串,当回文串右端点超过了now的时候,就对now和右端点之间的字符更新。
同理处理另一种情况,然后枚举取最大值即可。
 
PS:我们已知回文串的中点i,怎么求在回文串内的j以其为终点的回文串长度。
我们有结论为j-i+1,简易证明:
我们知道我们最开始的时候是含有“#”插入的字符串处理,此时回文串的长度为2*(j-i)+1.
而实际上其中包含了“#”有j-i个(就是长度/2向下取整)。
相减得到j-i+1。
 
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100010
using namespace std;
int left[2*N],right[2*N],mx,id,p[2*N];
char s[2*N];
int main(){
    scanf("%s",s+1);
    int l=strlen(s+1);
    s[0]='@';
    for(int i=l;i>=1;i--)s[i*2]=s[i];
    for(int i=1;i<=2*l+1;i+=2)s[i]='#';
    s[2*l+2]='?';
    l=2*l+1;
    for(int i=1;i<=l;i++){
    if(mx>i)p[i]=min(p[2*id-i],mx-i);
    else p[i]=1;
    while(s[i-p[i]]==s[i+p[i]])p[i]++;
    if(i+p[i]>mx){
        mx=i+p[i];
        id=i;
    }
    }
    int now=0;
    for(int i=1;i<=l;i++){
    if(i+p[i]-1>now){
        for(int j=now+1;j<=i+p[i];j++){
        left[j]=j-i+1;
        }
        now=i+p[i]-1;
    }
    }
    now=l+1;
    for(int i=l;i>=1;i--){
    if(i-p[i]+1<now){
        for(int j=now-1;j>=i-p[i];j--){
        right[j]=i-j+1;
        }
        now=i-p[i]+1;
    }
    }
    int ans=0;
    for(int i=2;i<=l;i+=2){
    ans=max(ans,left[i]+right[i+2]);
    }
    printf("%d\n",ans);
    return 0;
}
posted @ 2017-12-04 15:24  luyouqi233  阅读(338)  评论(0编辑  收藏  举报