Manacher算法

Manacher算法

Manacher 算法 又名 马拉车算法

也是回文自动机

此算法专门解决求回文串的问题

求回文串的问题 首先有暴力做法

1. Brute-force 解法

枚举每一个子串 判断是否回文

此方法当然是最好想到的暴力

但是时间复杂度显然是O(n^3)的

明显数据稍微大一点就不可过

2. Brute-force 优化版

此时枚举每个中点

向左右扩展

奇数个和偶数个分开讨论

此时的时间复杂度为O(n^2)的

那么怎么样才可以以O(n)的时间算出回文子串呢

这就需要manacher算法了

3.Manacher

首先的第一步

为解决奇数和偶数回文的问题

那么在每个字符之间加上一个’#”

借鉴hamsterwk的博客:以下内容转自博客

manacher算法,就是在上面O(n2)O(n2)的算法基础上,进行优化,使复杂度变为O(n)O(n).

它的主要优化在于:利用之前得到的回文串计算结果,来减少比对次数。

为实现此算法,我们额外维护两个数值:

MaxRightMaxRight:之前发现的回文串中,右端延伸到的位置最远的那个串延伸到的位置。

pospos:这个回文串的中心所在位置。

然后,在枚举中心的位置基础上,我们通过一些分析,来定义lenxlen>x的初值。

这就是manacher的具体原理,在上面那个框架的基础上,加入对lenxlenx的初值定义,减少计算次数。

考虑以下情况:

当前位置,在MaxRightMaxRight左侧:

在此情况下,i位置是包含在以pos为中心的回文串中的,我们定义其关于pos的对称点为j。

lenj+i1≤MaxRightlenj+i1≤MaxRight

这种情况如此图所示:
这里写图片描述

绿色部分是j为中心的回文串,而i,j是对称的,所以i为中心的这个与j的回文串对称的子串,也是一个回文串。因此,我们可以直接令leni=lenjleni=lenj。

lenj+i1>MaxRightlenj+i1>MaxRight

这种情况如此图所示:
这里写图片描述

由于红串内才保证是回文的,所以,虽然橘色很长,甚至延伸到了外面,但是我们只能取其“在红串中”的回文部分。所以,我们取绿色部分(即leni=MaxRight−ileni=MaxRight−i

当前位置,在MaxRightMaxRight右侧:

这个时候,由于其不在回文串中,所以我们必须从头开始进行比较过程,也就是leni=1leni=1

分类讨论之后,就还是按照上述方法,进行拓展。
拓展结束之后,别忘了更新MaxRight和pos

复杂度证明

还是按照上述情况分析:

此情况下,其实不会有任何拓展。。。
因为如果能拓展,在处理j的时候就做到了。。。

此情况下,等于直接从MaxRight处开始向外拓展,拓展一次,MaxRight就会增加一次。

另外的情况下,==相当于把pos移动到当前的i,同时也一定会增加MaxRight。==

(Mixright只增不减)

综上,每次操作,要么增加MaxRight,要么增加pos,两者都小于字符串长度,所以复杂度为O(n)。

代码部分

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
char tmp[2500];
char s[5000];
int len[5000];
int maxright=-1,pos=0;
int L;
void pre(){
    L=strlen(tmp+1);
    L*=2;
    printf("%d\n",L);
    for(int i=0;i<=L;i+=2)
        s[i]='#';
    int p=0;
    for(int i=1;i<L;i+=2)
        s[i]=tmp[++p];
        printf("L=%d\n",L);
    printf("%s\n",s);
}
void manacher(){
    for(int i=0;i<=L;i++){
        int j=pos*2-i;
        if(i<maxright)//i在maxright左边 
            len[i]=min(len[j],maxright-i);
        else len[i]=1;
        while(i+len[i]-1<=L&&i-len[i]+1>=0&&s[i+len[i]-1]==s[i-len[i]+1])//左右扩 
            len[i]++;
        len[i]--;//while多循环一次 
        if(i+len[i]-1>=maxright){
            maxright=i+len[i]-1;
            pos=i;          
        }
        printf("maxright=%d\n",maxright);
    }
}
int main(){
    scanf("%s",tmp+1);
    pre();
    manacher();
    for(int i=0;i<=L;i++)
        printf("%d ",len[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值