【算法詳解】:Manacher

問題導入:

現在有一個長度SS的字符串,現在需要求出這個字符串中的最大回文子串。


算法舉例:

  • 最樸素算法,枚舉迴文串的對稱中心,分別先左和向右擴展,依次更新最大值。算法複雜度O(n2)O(n^2)
  • HashHash+ 二分:計算字符串的前綴HashHash值,枚舉中點,二分迴文字串的長度。算法複雜度O(nlogn)O(n log n)
  • 迴文自動機,代碼複雜,思維難度大,算法複雜度O(n)O(n)
  • ManacherManacher,代碼簡單,思維難度不高,算法複雜度O(n)O(n)

顯然,ManacherManacher是一種簡單有實惠的算法,現在來解釋一下這個算法。


Manacher算法:

迴文串的對稱中心有奇有偶,判斷起來有點麻煩,這裏引入一個小技巧,在每個字符的左右都插入‘#’,
S="aabb"S="aabb ",在插入字符後就變成了S="S="#aa#aa#bb#bb "",可以在字符串的首尾加入22個奇怪的字符,以防數組越界。

我們還需要引入輔助數組p[i]p[i],表示以ii爲對稱中心的最長迴文串的半徑長度,例如下圖:
在這裏插入圖片描述
顯然可知,max{p[i]1}max\{p[i]-1\}就是這個字符串中的最大回文子串。因此ManacherManacher算法的根本目的就是求出上述的所有p[i]p[i]

我們使用mxmx表示當前的迴文子串的右邊界,idid表示當前迴文子串的對稱中心,如圖:
在這裏插入圖片描述
如果i<mxi<mxjjii對於idid的對稱點,j ⁣= ⁣2id ⁣1j\!=\!2*id-\!1,因爲ididmxmx的子串等於mxmx的對稱點到idid的子串,所以在mxmx的對稱點到jj的最大回文長度就是iimxmx的迴文長度,那麼p[i]p[i]的初始值就是p[i]=min(p[j],mxi)p[i]=min(p[j],mx-i)

如果i>mxi>mx,那麼說明在ii沒有相應的對稱點,那麼p[i]p[i]的初始值就是11

初始值確定後,就根據初始值向兩邊暴力比較擴展,且注意更新ididmxmx的值。

Manacher複雜度分析:

如果i<mxi<mxp[i]=p[j]p[i]=p[j],那麼單次求解的時間複雜度爲O(1)O(1)
反之,那麼一定迴向外擴展,就會使mxmx更新,使之增大,複雜度是O(n)O(n)的。

綜上所述,ManacherManacher算法的複雜度是線性的。

代碼如下:

#include <bits/stdc++.h>
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<<tot<<' ';
	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<<p[i]<<' ';
	printf("%d\n",ans-1);
	return 0;
} 
課後例題:
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章