【算法详解】:Manacher

问题导入:

现在有一个长度 S S S的字符串,现在需要求出这个字符串中的最大回文子串。


算法举例:

  • 最朴素算法,枚举回文串的对称中心,分别先左和向右扩展,依次更新最大值。算法复杂度 O ( n 2 ) O(n^2) O(n2)
  • H a s h Hash Hash+ 二分:计算字符串的前缀 H a s h Hash Hash值,枚举中点,二分回文字串的长度。算法复杂度 O ( n l o g n ) O(n log n) O(nlogn)
  • 回文自动机,代码复杂,思维难度大,算法复杂度 O ( n ) O(n) O(n)
  • M a n a c h e r Manacher Manacher,代码简单,思维难度不高,算法复杂度 O ( n ) O(n) O(n)

显然, M a n a c h e r Manacher Manacher是一种简单有实惠的算法,现在来解释一下这个算法。


Manacher算法:

回文串的对称中心有奇有偶,判断起来有点麻烦,这里引入一个小技巧,在每个字符的左右都插入‘#’,
S = " a a b b " S="aabb " S="aabb",在插入字符后就变成了 S = " S=" S="# a a a# a a a# b b b# b b b " " ",可以在字符串的首尾加入 2 2 2个奇怪的字符,以防数组越界。

我们还需要引入辅助数组 p [ i ] p[i] p[i],表示以 i i i为对称中心的最长回文串的半径长度,例如下图:
在这里插入图片描述
显然可知, m a x { p [ i ] − 1 } max\{p[i]-1\} max{p[i]1}就是这个字符串中的最大回文子串。因此 M a n a c h e r Manacher Manacher算法的根本目的就是求出上述的所有 p [ i ] p[i] p[i]

我们使用 m x mx mx表示当前的回文子串的右边界, i d id id表示当前回文子串的对称中心,如图:
【算法详解】:Manacher_第1张图片
如果 i < m x ii<mx j j j i i i对于 i d id id的对称点, j  ⁣ =  ⁣ 2 ∗ i d −  ⁣ 1 j\!=\!2*id-\!1 j=2id1,因为 i d id id m x mx mx的子串等于 m x mx mx的对称点到 i d id id的子串,所以在 m x mx mx的对称点到 j j j的最大回文长度就是 i i i m x mx mx的回文长度,那么 p [ i ] p[i] p[i]的初始值就是 p [ i ] = m i n ( p [ j ] , m x − i ) p[i]=min(p[j],mx-i) p[i]=min(p[j],mxi)

如果 i > m x i>mx i>mx,那么说明在 i i i没有相应的对称点,那么 p [ i ] p[i] p[i]的初始值就是 1 1 1

初始值确定后,就根据初始值向两边暴力比较扩展,且注意更新 i d id id m x mx mx的值。

Manacher复杂度分析:

如果 i < m x ii<mx p [ i ] = p [ j ] p[i]=p[j] p[i]=p[j],那么单次求解的时间复杂度为 O ( 1 ) O(1) O(1)
反之,那么一定回向外扩展,就会使 m x mx mx更新,使之增大,复杂度是 O ( n ) O(n) O(n)的。

综上所述, M a n a c h e r Manacher Manacher算法的复杂度是线性的。

代码如下:

#include 
using namespace std;

const int N = 31001000;
int n, m;
int p[N] = {}, tot = 0;
char s[N];

inline void input() {
	char ch = getchar();
	while (ch >= 'a' && ch <= 'z') 
		s[++ tot] = ch, s[++ tot] = '#', ch = getchar();
}

int main() {
	s[tot] = '$';
	s[++ tot] = '#'; 
	input();
	int ans = 0;
	int mx = 0, id = 0;
//	cout<
	for (int i=1;i<=tot;i++) {
		if (i < mx) p[i] = min(p[(id<<1)-i], mx-i);
			else p[i] = 1;
		while (s[i-p[i]] == s[i+p[i]]) p[i] ++;
		if (i + p[i] > mx) mx = i + p[i], id = i;
		ans = max(ans, p[i]);
	}
//	for (int i=0;i<=tot;i++) cout<
	printf("%d\n",ans-1);
	return 0;
} 
课后例题:
  • B Z O J 2084 BZOJ2084 BZOJ2084 A n t i s y m m e t r y Antisymmetry Antisymmetry
    题解见 https://blog.csdn.net/zhuchangcheng1003/article/details/102165103

你可能感兴趣的:(算法详解)