【学习笔记】Manacher算法
Manacher(中文:马拉车)算法,即求解给定字符串中最长回文子串长度的算法。
例题:
给出一个长度为 \(n\) 的只由小写英文字符\(\mathtt{a,b,c...y,z}\) 组成的字符串 \(S\) ,求 \(S\) 中最长回文串的长度 。
算法流程
Step 1
一般情况下回文串有奇偶分类。
为了避免分类,我们在字符串中间添加特殊字符(不妨用 ‘#’),在串的首尾加上不同的特殊字符(不妨用 ‘&’ 和 ‘@’)。
例:串 S = baabcoc 转化为串 S_new = &#b#a#a#b#c#o#c#@
这样就将字符串中原有的回文串都转化成了奇回文串。
Step 2
我们构造一个辅助数组 \(p\) ,设 \(p[i]\) 表示以 \(i\) 为中心的最长回文子串(新串)的半径,则以 \(i\) 为中心的最长回文子串(原串)的长度为 \(p[i]-1\)
例:

接下来考虑 \(p[i]\) 的更新。
设变量 \(id\),\(mr\),其中 \(mr\) 表示以 \(id\) 为中心的最长回文子串的右端位置的下一个位置,即 \(mr=id+p[id]\)
那么就有:
if(i<mr)
p[i]=min(p[2*id-i],mr-i);
\(2 \times id-i\) 表示 \(i\) 关于 \(id\) 对称后的位置,而 \(mr-i\) 表示从 \(i\) 到回文子串右端点的长度。
设 \(j=2 \times id-i\)

如果 \(p[j]<mr-i\),则根据代码 \(p[i]=p[j]\),如果 \(p[i]\) 还能更大,那么根据对称,\(p[j]\) 也应该更大(由于 \(p[j]<mr-i\),所以 \(p[j]\) 至少可能大1),这就与 \(p[j]\) 已求最大值矛盾。
如果 \(p[j]>mr-i\),则根据代码 \(p[i]=mr-i\),如果 \(p[i]\) 还能更大,那么根据对称,\(p[id]\) 也会更大(从关于 \(i\)、\(j\)、\(id\) 对称三个方面可推导),这就与 \(p[id]\) 已求最大值矛盾。
其他情况就通过暴力求解。
代码附下:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=11000010;
int len,p[2*maxn],j=2;
char n[maxn],a[2*maxn];
int main(){
cin>>n;
len=strlen(n);
a[1]='$';a[2]='#';
for(int i=0;i<len;i++){
a[++j]=n[i];
a[++j]='#';
}a[++j]='@';
len=j;
int mr=0,id=0,max_len=-1; //初始化:把id和mr按第一个'$'来赋值(mr=1+p[1]=2)
for(int i=2;i<len;i++){ //从第一个'#'到最后一个'#'
if(i<mr)
p[i]=min(p[2*id-i],mr-i);
else p[i]=1; //准备进行暴力
while(a[i-p[i]]==a[i+p[i]])//不需边界判断,因为左有'&',右有'@'标记;
p[i]++;
if(mr<i+p[i]){id=i,mr=i+p[i];} //使mr尽量大以减少时间
max_len=max(max_len,p[i]-1);
}
cout<<max_len;
return 0;
}
时间复杂度
从代码中可以看出 \(mr\) 只会不断扩展,且在 \(mr\) 以内不会进行暴力扩展(在前面计算 \(p[id]\) 时都考虑过了),所以均摊复杂度是 \(O(n)\)。

浙公网安备 33010602011771号