hdu3068+hdu3294 最长回文字符串的manacher算法

两道题目描述都差不多,关键问题都是求最长回文子串,这里讲解一下manacher算法。

对于一个字符串,记初始下标为0,回文串有偶回文和奇回文之分,可以在字符串任意两个字符之间加入’#‘,使得奇回文还是奇回文,偶回文变成奇回文。

然后增加一个数组p,p[i]表示以i为中心的最长回文串的半径。增加一个整型变量mx,记录所有已经计算过的部分的回文串能达到的最右端的部分,增加一个整型变量id,记录已经计算过的部分的最大mx的中心,

例如对asabba这个字符串,边模拟边讲思想。

变成@#a#s#a#b#b#a#,@相当于终结符合,保证它在输入字符串中没有出现过,则每次匹配到此处时不可能匹配成功,防止数组越界。

初始id=mx=p[0]=0。

接下来对p[1],试想,如果1<mx,即我当前正要计算的位置在之前已经计算过的最大回文串最右边的左边,那么在此处与id对称的位置上,一定与此处的字符匹配,因为id的匹配回文长度长于当前位置,然后,既然处于已经匹配成功的串内部,那么,与id对应位置的哪个字符的最长回文长度对当前位置就是可用的,如果哪个长度小于mx-i,那么毫无疑问,那边能回文,而我这边与那边完全对称,那我这边肯定也回文,如果大于mx-i,那么至少在mx-i的长度内是完全回文的,那么当前位置的最小回文长度为mx-i。求得最小值之后,就要进行简单模拟,试图增大回文串长度,直至匹配失败。

由于1>mx=0,之前的所有计算结果都不涉及1,故前面所有内容对当前位置无用,p[1]=0。试图增大发现s[0]!=s[2],匹配失败,p[1]=0,id=mx=1。

由于2>mx=1,同上,p[2]=0。试图增大发现s[1]==s[3],s[0]!=s[4],故p[2]=1,此时最大位置发生变化,mx=i+p[i]=3,id=2。

由于3==mx,之前匹配结果对我有用,1是3关于id的对称位置,由于p[1]==0,mx-i==0,则p[3]=0。试图增大,增大失败,p[3]=0,i+p[i]<=mx,没有改变mx的最大值,故id和mx不变。

由于4>mx,同上读者自己推,得p[4]=3,mx=4+p[4]=7,id=4。

由于5<mx,之前结果对当前位置有用,3是5关于4的对称点,p[3]=0,而5和3对称,则p[5]=0。试图增大,得p[5]=0。

由于6<mx,之前结果对当前位置有用,2是6关于4的对称点,p[2]=1,而2和6对称,且mx-6>=p[2],证明6到mx部分和2-p[2]到2的部分对称,2和6之内的部分对称,2形成的最长回文串在4形成的最长回文串之内,p[6]=p[2](算法精髓,动态规划思想,读者认真体会)。然后试图增大,得p[6]=1。

由于7<=mx,之前结果有用,算的p[7]=p[1]=0。试图增大,p[7]=0。

由于8>mx,之前结果无用,p[8]=0。朴素算法继续试图增大,p[8]=1,mx=9,,id=8。

由于9=mx,之前结果有用,算的p[9]=0。试图增大,得p[9]=4,mx=13=字符串长度,id=9。

此后的部分其实可以不算,因为之后以任意一个字符为中心形成的回文串最长也只能到字符串尾部,而上面部分已达到尾部,则上面部分形成的回文串一定长于此后部分的回文串,可以直接跳出。

算法的关键部分在于已经求得的回文串的利用,对称的字符串,其子字符串相应位置必然对称,然后从前往后求时,对对称部分只需要求一次便可,节省了时间。

网上说算法是o(n)的,但感觉不是o(n)的,具体时间复杂度随机性应该比较大。

hdu3068代码(模板):

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

using namespace std;

char s[222222];
char s1[111111];
int p[222222];

int min(int a,int b)
{
    return a>b?b:a;
}

void getp()
{
    int id=0,mx=0;
    p[0]=0;
    int l=strlen(s);
    for (int i=1;i<l;i++)
    {
        p[i]=mx<i?0:min(p[id*2-i],mx-i);
        while (s[i-p[i]-1]==s[i+p[i]+1])
            p[i]++;
        if (p[i]+i>mx)
        {
            mx=p[i]+i;
            id=i;
        }
    }
}

int main()
{
    while (~scanf("%s",s1))
    {
        int l=strlen(s1);
        s[0]='@';
        for (int i=0;i<=l;i++)
        {
            s[i*2+1]='#';
            s[i*2+2]=s1[i];
        }
        getp();
        int ans=0;
        l=strlen(s);
        for (int i=2;i<l;i++)
        {
            if (p[i]>ans)
                ans=p[i];
        }
        cout<<ans<<endl;
    }
}

hdu3294代码(输出麻烦点):

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

using namespace std;

char s1[200002];
char s[400004];
char pp;
int p[400004];

int min(int a,int b)
{
    return a>b?b:a;
}

void getp()
{
    int id=0,mx=0;
    int l=strlen(s);
    p[0]=0;
    for (int i=1;i<l;i++)
    {
        p[i]=mx<i?0:min(p[id*2-i],mx-i);
        while (s[i-p[i]-1]==s[i+p[i]+1])
            p[i]++;
        if (i+p[i]>mx)
        {
            mx=p[i]+i;
            id=i;
        }
    }
}

int main()
{
    while (~scanf("%c%s",&pp,s1))
    {
        s[0]='@';
        int l=strlen(s1);
        for (int i=0;i<=l;i++)
        {
            s[2*i+1]='#';
            s[2*i+2]=s1[i];
        }
        getp();
        /*
        for (int i=0;i<strlen(s);i++)
            cout<<i<<" ";
        cout<<endl;
        for (int i=0;i<strlen(s);i++)
            cout<<s[i]<<" ";
        cout<<endl;
        for (int i=0;i<strlen(s);i++)
            cout<<p[i]<<" ";
        cout<<endl;
        */
        int id,ans=0;
        for (int i=2;i<=l*2;i++)
        {
            int t=p[i];
            if (t>ans)
            {
                ans=t;
                id=(i+1)/2-1;
                id=id-(t+1)/2+1;
                if (i%2==1)
                    id--;
            }
        }
        if (ans<=1)
        {
            cout<<"No solution!"<<endl;
            getchar();
            continue;
        }
        cout<<id<<" "<<id+ans-1<<endl;
        for (int i=id;i<id+ans;i++)
        {
            char a=s1[i];
            int t=pp-'a';
            a=a-t;
            if (a<'a')
                a=a+26;
            cout<<a;
        }
        cout<<endl;
        getchar();
    }
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值