BZOJ2342: [Shoi2011]双倍回文【Manacher】

题意:

记字符串A。A反转过来是B。在给定的一个字符串里找到最长的形如ABAB的子串。这个子串称为双倍回文。

 

考虑用Manacher算法。(不会的自行google即可呃)

首先每个字符之间加一个分隔符#。然后跑一遍Manacher得到p数组。

拿字符串abbaabba举例子。如图。

然后我们从小到大开始枚举中心点i。

因为从i往右扩展的最远的地方是r=i+p[i]-1。那么我们只要在i~(i+r)/2中找到一点j使得j-p[j]+1<=i,也就是说s[i..j]是回文串。根据对称性,那么s[j..j+j-i]也是回文串。同理左半边也是回文串。那么就满足双倍回文的条件了。因为找的是最长的,所以j要尽可能大。

举个例子,假设枚举到i=9的时候。最大可以扩展到17。那么这个时候我们只要在9~13((9+17)/2=13)中找到一个最大的j,满足j-p[j]+1<=i。明显j=13。j-p[i]+1=13-5+1=9。

所以可以更新答案。ans=max(ans,(j-i)*4)。最后ans要除2。因为有一半的字符是#。

 

做的时候呢,记录每个点i扩展到的最左点l[i]=i-p[i]+1,和i一起排个序。枚举到一个点k的时候,把所有的l[i]<=k的i插入treap。然后查询treap中小于等于(k+k+p[k]-1)/2的最大值。更新答案。

接下来就好做了。但是还是有些小细节。导致我WA数次。。

 

第一,枚举的中心点如果是字母不是#的话,明显不可行。为什么呢。因为这个答案串长是4的倍数。那么对称轴是虚的,不可能在子母上。因此枚举的中心点必须是#,所以下标一定是奇数。

第二,同理,中心点i扩展到最远的r=i+p[i]-1,这个r也一定是奇数。很明显r如果是偶数,那一定可以扩展到r+1。因为对称的所以两边都多一个#没问题。否则答案串的字母比#多。除2就不对了。

 

另外的话,边界问题小心一点。可以从第5个开始扫描,扫描到倒数第5个。

 

贴代码~

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 1050005

int p[maxn],u[maxn]={0};
char s[maxn],str[maxn];
void manacher(int n){
    int id,mx=0;
    p[0]=1;
    for(int i=1;i<n;i++){
        p[i]=1;
        if(mx>i)    p[i]=min(p[id*2-i],mx-i);
        while(str[i+p[i]]==str[i-p[i]])  p[i]++;
        if(i+p[i]>mx)   mx=i+p[i],id=i;
    }
}

struct Treap{
    int val[maxn];
    int pri[maxn],ch[maxn][2],tot,rt,size;;
    inline void rotate(int &x,int f){
        int y=ch[x][!f];
        ch[x][!f]=ch[y][f];
        ch[y][f]=x;
        x=y;
    }
    inline void ins(int &x,int v){
        if(x==0){
            val[tot]=v;
            pri[tot]=rand();
            ch[tot][0]=ch[tot][1]=0;
            x=tot++;
        }else{
            int f=v<val[x];
            ins(ch[x][!f],v);
            if(pri[x]<pri[ch[x][!f]])rotate(x,f);
        }
    }
    inline int pred(int x,int y,int k){
        if(!x)return y?val[y]:-1;
        else if(k<val[x])return pred(ch[x][0],y,k);
        else return pred(ch[x][1],x,k);
    }
    inline void ins(int v){     size++;ins(rt,v);       }
    inline int getpre(int x){   return pred(rt,0,x);    }
    inline void init(){ tot=1;  rt=0;   size=0; } 
}tr;

typedef pair<int,int> pii;
pii a[maxn];

int main()
{
    int len;
    scanf("%d",&len);
    scanf("%s",s);
    int t=0;
    str[t++]='?';
    str[t++]='#';
    for(int i=0;s[i];i++){
        str[t++]=s[i];
        str[t++]='#';
    }
    manacher(t);
    for(int i=1;i<t;i++)    a[i]=make_pair(i-p[i]+1,i);
    sort(a+1,a+t);
    tr.init();
    tr.ins(-1);
    int cur=1,ans=0;
    for(int i=5;i<=t-5;i++){
        while(a[cur].first<=i && cur<t){
            tr.ins(a[cur].second);
            u[a[cur].second]=1;
            cur++;
        }
        if((i&1)==0)continue;
        int t=(i+i+p[i]-1)>>1;
        int p=tr.getpre(t);
        if(p==-1)continue;
        if(p%2==0){
            p--;
            while(p>i+1 && !u[p])p-=2;
        }
        if(p<=i+1)continue;
        if(p-i>1 && (p-i)*4>ans)    ans=(p-i)*4;
    }
    printf("%d\n",ans>>1);
    return 0;
}


 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值