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+i−1≤MaxRightlenj+i−1≤MaxRight
这种情况如此图所示:
绿色部分是j为中心的回文串,而i,j是对称的,所以i为中心的这个与j的回文串对称的子串,也是一个回文串。因此,我们可以直接令leni=lenjleni=lenj。
lenj+i−1>MaxRightlenj+i−1>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;
}