【学习笔记】Manacher算法

Manacher(中文:马拉车)算法,即求解给定字符串中最长回文子串长度的算法。

例题:

洛谷P3805

给出一个长度为 \(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\)

例:

JA5VJS.png

接下来考虑 \(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\)

JA5qyj.jpg

如果 \(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)\)

posted @ 2020-04-07 08:48  Captain-01  阅读(119)  评论(0)    收藏  举报