Manacher算法详解

背景


1975年,一个叫Manacher的人发明了一个算法,Manacher算法(中文名:马拉车算法),该算法可以把时间复杂度降到到O(n)。

manacher解决的问题是:求最大的回文子串长度,例如“123211”的最长回文子串是“12321”,长度为4;

正常暴力算法要遍历每个字母,字符串一长就会超时,但其实暴力的方法做了些无用功,manacher就是对其的优化。

 

代码 


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

题意:求最长回文子串长度

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=11e4+10;//相当于110000+10,e后是几就是几个零
char str[N<<1];//位运算,相当于N*2,为什么要两倍空间?
int p[N<<1];
int main()
{
    while(scanf("%s",str)!=EOF&&strlen(str))//空字符退出
    {
        int len=strlen(str);
        for(int i=len;i>=0;i--)
        {
            str[2*i+2]=str[i];
            str[2*i+1]='#';
        }
        str[0]='$';
        len=2*len;//2*len的位置对应原字符串的最后一个字符
        int id=0,mx=0,res=0;
        for(int i=0;i<=len;i++){
            if(i<mx)
                p[i]=min(p[2*id-i],mx-i);//这就是优化,其实真的不难
            else
                p[i]=1;
            while(str[i+p[i]]==str[i-p[i]])
                p[i]++;
            if(p[i]+i>mx){
                mx=p[i]+i;
                id=i;
            }
            if(p[i]-1>res) res=p[i]-1;
        }
/*
        for(int i=0;i<=len;i++)
            printf("%d -> %c -> %d\n",i,str[i],p[i]);
*/
        printf("%d\n",res);

    }
    return 0;
}

现在有很多问题,先不着急讲manacher所做的优化,先思考怎么找回文串,回文串分奇回文(aba)和偶回文(abba),这就要分情况讨论了,注意我们现在只是以暴力的思路来写,不用考虑别的,我们可以把所有的字符串改成奇回文的形式,

例如:“abba”-->“#a#b#b#a#”

很明显这样做可以把所有的字符串都变成只考虑奇回文的情况,之后我们设一个数组p来存放回文串长度,先全赋值为1,再找最大长度。

    while(scanf("%s",str)!=EOF&&strlen(str))
    {
        int len=strlen(str);
        for(int i=len;i>=0;i--)
        {
            str[2*i+1]=str[i];
            str[2*i+2]='#';
        }
        str[0]='#';
        len=len*2;
        for(int i=0;i<len;i++){//这和上面的代码没关系
            p[i]=1;
            while(str[i+p[i]]==str[i-p[i]])
                p[i]++;
        }

        for(int i=0;i<=len;i++)
            printf("%d -> %c ->%d\n",i,str[i],p[i]);
    }

这样子好像没什么问题,但我们输入“aaaa”就发现问题了;

4对应的“#a#a#a#a#”应该是5才对啊,为什么是6呢?这其实是数组越界,我们再while中加上一些判断,看指向到了什么地方

if(p[i]==5)
    printf("i+p[i]=%d i-p[i]=%d\n",i+p[i],i-p[i]);

很明显在触碰边界时并没有停止,那为什么不继续等于-2,-3呢?这我也不清楚,总之会越界对吧,所以我们应该判断一下防止越界,具体做法就是在最前端加个独一无二的字符表示边界,为了保险,可以在末尾加上‘\0’,实际上不加这个末尾也行,如果最后又出错,不妨看看是不是这个原因,我还没碰到内种情况。还要注意不要换成“$a#b#b#a#”这样会破坏结构,我们只是要防止越界而以。

 

优化


到这,暴力的方法算是成型了,但是manacher怎么优化的呢?首先理解 i+p[i] 是最右的边界,因为从左到右i递增,所以 id+p[id] 可以说是id的领域,在这个领域里有什么不同?这就是关于id左右对称,我们如果先得到左边的p,到右边的对称点不就情况一致吗(先不考虑之后的字符影响),但是如果左边还受到超出领域的字符的影响就不是了,那就尽可能的取最大的,就是左边的位置减去左边界(等于右边界减去右边的位置),结合代码,这就是( p[ id - ( i - id ) ] )和 ( mx - i )

马拉车就是做了个这么的优化,理解了这些就可以去看看别的题目找找感觉了,这是最基础的。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值