Manacher算法(回文串匹配算法)

算法参考:http://www.cnblogs.com/biyeymyhjob/archive/2012/10/04/2711527.html

此算法用于求回文串,可以达到o(n)的复杂度,较好的解决问题,抛去了奇数或偶数匹配的差别,下面进行应用,通过题目来讲解


题目:http://acm.split.hdu.edu.cn/showproblem.php?pid=3613

题意:给定一个字符串和一个26个价值的数组(代表26字母对应的价值),要求分成两段,其中如果一段满足回文,价值为价值和,否则为0,统计最大价值

题解:利用manacher算法进行回文串预处理,再从前往后扫描和从后往前扫描回文串,统计最大的和

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#define N 500050

using namespace std;
int zi[28]; //记录价值
char a[N];  //记录原始字符串
char b[N*2]; //记录manacher算法预处理后的字符串
int p[N*2];  //记录p值
int first[N*2];  //记录从前扫描的,以第一个字符开始的回文串
int last[N*2];  //记录从后扫描,以最后一个字符结束的回文串
int f,l;      //记录以上两数组的数量
int sum[N*2];  //统计从1-i的和,要排除‘#’
void Manacher(char *c,int len)
{
    f=0;l=0;
    int id=0,mx=0;
    for(int i=1;i<len;i++)
    {
        sum[i]=sum[i-1];
        if(c[i]!='#'){sum[i]+=zi[c[i]-'a'];}
        if(mx>i)
        {
            p[i]=p[id*2-i]>=mx-i?mx-i:p[id*2-i];
        }
        else
        {
            p[i]=1;
        }
        while(c[i-p[i]]==c[i+p[i]]&&i+p[i]<len)
        {
            p[i]++;
        }
        if(mx<i+p[i])
        {
            mx=i+p[i];
            id=i;
        }
        if(i==p[i]&&p[i]!=1&&i+p[i]!=len)//以第一个字符(非#)为开始的回文串
        {
            first[f++]=i;
        }
        if(i+p[i]==len&&p[i]!=1&&i!=p[i])//以最后一个字符(非#)为结束的回文串
        {
            last[l++]=i;
        }
    }
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        for(int i=0;i<26;i++) scanf("%d",&zi[i]);
        scanf("%s",a);
        int len=strlen(a);
        int blen=2;
        b[0]='$';b[1]='#';
        for(int i=0;i<len;i++)
        {
            b[blen++]=a[i];
            b[blen++]='#';
        }
        Manacher(b,blen);
        int ans=0;
        int s=0;
        for(int i=0;i<f;i++)  //从前统计价值
        {
            int d=first[i]+p[first[i]]-1;
            int d2=(blen-1+d)/2;
            s=sum[d];
            if(p[d2]+d2==blen){s=sum[blen-1];}  //判断后面是否也构成回文串,为保证后面为奇数要思想上去掉最后的‘#’
            if(ans<s) ans=s;
        }
        for(int i=0;i<l;i++)//从后统计价值
        {
            int d=last[i]-p[last[i]]+1;
            int d2=(1+d)/2;
            s=sum[blen-1]-sum[d-1];
            if(d2-p[d2]==1){s=sum[blen-1];}
            if(ans<s) ans=s;
        }
        printf("%d\n",ans);

    }
    return 0;
}


题目:http://acm.split.hdu.edu.cn/showproblem.php?pid=5340

题意:给定一个字符串,问能否分成三个回文串

题解:利用manacher进行预处理,要先扫描从第一个字符开始的回文串和以最后一个字符为结尾的回文串,然后一个个枚举,是否满足中间的剩余字符串也为回文串

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#define N 20050

using namespace std;
char a[N]; 
char b[N*2];
int b_p[N*2];
int first[N*2];
int last[N*2];
void Manacher(char *c,int len)
{
    int id=0,mx=0;
    for(int i=1;i<len;i++)
    {
        if(mx>i)
        {
            b_p[i]=b_p[2*id-i]>=mx-i?mx-i:b_p[2*id-i];
        }
        else
        {
            b_p[i]=1;
        }
        while(i+b_p[i]<len&&c[i-b_p[i]]==c[i+b_p[i]])
        {
            b_p[i]++;
        }
        if(mx<b_p[i]+i)
        {
            mx=b_p[i]+i;
            id=i;
        }
    }
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int blen=2;
        scanf("%s",a);
        int len=strlen(a);
        if(len<3)
        {
            printf("No\n");
        }
        else if(len==3)
        {
            printf("Yes\n");
        }
        else
        {
            b[0]='$';b[1]='#';
            for(int i=0;i<len;i++) //预处理
            {
                b[blen++]=a[i];
                b[blen++]='#';
            }
            Manacher(b,blen);
            int flag=0;
            int l=0,r=0;
            for(int i=1;i<blen;i++)//扫描
            {
                if(i-b_p[i]+1==1)
                {
                    if(b[i]=='#'&&b_p[i]==1){}
                    else{first[l++]=b_p[i];}
                }
                if(i+b_p[i]-1==blen-1)
                {
                    if(b[i]=='#'&&b_p[i]==1){}
                    else{last[r++]=b_p[i];}
                }
            }
            for(int i=0;i<l;i++)
            {
                for(int j=r-1;j>=0;j--)
                {
                    int d=2*first[i]-1+blen-2*last[j]+1;  //求第一个回文串的结束位置和最后一个回文串的开始位置之和
                    int sub=blen-2*first[i]-2*last[j]+1;  //求第一个回文串的结束位置和最后一个回文串的开始位置之差,即是中间字符串的长度
                    if(sub<=0) break;  //中间字符串不能为空
                    if(b_p[d/2]*2-1>=sub)//若中间字符是以该字符为中心的总长大于中间长度的回文串则符合条件
                    {
                        flag=1;break;
                    }
                }
                if(flag) break;
            }
            if(flag) printf("Yes\n");
            else printf("No\n");
        }
    }
    return 0;
}

题目:http://acm.split.hdu.edu.cn/showproblem.php?pid=3068

题意:求最大回文串长度

题解:manacher模板题

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>

using namespace std;
char a[110050*2];
char b[110050*2];
int p[110050*2];
int Manacher(char *c,int len)
{
    int mx=0,id=0;
    int ans=0;
    for(int i=1;i<=len;i++)
    {
        if(mx>i)
        {
            p[i]=p[2*id-i]>=mx-i?mx-i:p[2*id-i];
        }
        else
        {
            p[i]=1;
        }
        while(c[i-p[i]]==c[i+p[i]])
        {
            p[i]++;
        }
        if(i+p[i]>mx)
        {
            mx=p[i]+i;
            id=i;
        }
        if(ans<p[i])
        {
            ans=p[i];
        }
    }
    return ans;
}
int main()
{
    while(~scanf("%s",a))
    {
        int len=strlen(a);
        int co=2;
        b[0]='$';b[1]='#';
        for(int i=0;i<len;i++)
        {
            b[co++]=a[i];
            b[co++]='#';
        }
        int d= Manacher(b,co-1);
        printf("%d\n",d-1);
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值