Manacher's Algorithm (馬拉車算法)

用來查找一個字符串中最長迴文子串的方法ios

平時的暴力爲 \(n^3\) ,而\(Manacher\)將時間複雜度提高到了線性,算法

\(n^3\) 實在太……,想到優化數組

枚舉每個字符,並以它爲中心,向兩邊尋找回文串,當遍歷完整個數組的後,就能夠找到最長的迴文串,時間複雜度 \(O(n^2)\)優化

\(Manacher\) 只需 \(O(n)\)spa

迴文串的長度可奇可偶,aba(奇),abba(偶)code

預處理(在每個字符左右都加'#')那麼不管奇偶,字符的個數都成了奇數,避免了分類討論blog

$aba -->  #a#b#a#$
$abba --> #a#b#b#a#$

類比 \(KMP\) 算法,咱們處理一個P數組,\(P[i]\)表示以\(a[i]\)字符爲中心的迴文子串的半徑(若\(P[i] = 1\),則該回文串就是 \(a[i]\) 自己)字符串

關於半徑

很明顯咱們求出最長的半徑就知道最長迴文串字符的個數,爲啥??string

舉個栗子:io

A:# 1 # 2 # 2 # 1 # 2 # 2 #
p:1 2 1 2 5 2 1 6 1 2 3 2 1

顯然以中間'1'爲中心的迴文串半徑最大爲6;原串爲22122,長度爲5,正好爲半徑減一
奇數的舉例也是如此,因此該辦法可靠

關於起始位置

咱們若是知道半徑長度,但彷佛沒法定位子串,因此咱們還要知道它的起始位置

solution:咱們再在原串起始位置加一個$,因此起始位置就是中間位置減去半徑再除以2

關於爲何加$,(避免與原串字符重複,進而沒必要改變P數組值)

舉慄驗證(實在不會啥證實方法,望大佬能夠提供別的方法)

$#b#o#b# 中'o'的位置是 4 ,半徑是 4,相減爲 0,再除 2,依然是 0;

$#1#2#2#1#2#2# 中間'1'的位置爲 8,半徑是 6,相減爲 2,再除 2 是 1 因此原串中最長子串'22122'起始位置爲 1;

關於 \(P\) 數組的求法

有關變量:\(mx\):迴文串能延伸到的最右端的位置;\(id\):爲能延伸到最右端的位置的那個迴文子串的中心點位置

核心代碼:

p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;

====================================================================================
對稱點:

\(2 * id - i\)\(j\) 關於中點 \(id\) 的對稱點
\((i + j) / 2 = id\) 方程兩邊同時乘以二, 得:\(i + j = 2 * id\) 移項, 得:\(j = 2 * id - i\)

=========================================================================================

1.當 \(mx - i > P[j]\) 的時候,以\(A[j]\)爲中心的迴文串必然包含在以\(A[id]\)爲中心的迴文子串中,因此必有P[i] = P[j], 見圖

2.當 \(A[j] >= mx - i\) 的時候,以\(A[j]\)爲中心的迴文子串不必定在
\(A[id]\) 爲中心的迴文子串中,但根據對稱,下圖中兩個綠框所包圍的部分是相同的,也就是說以 \(A[i]\) 爲中心的迴文子串,其向右至少會擴張到\(mx\)的位置,也就是說 \(A[i] >= mx - i\) 至於 \(mx\) 以後的部分是否對稱,就只能老老實實去匹配了

3.對於 \(mx <= i\) 的狀況,沒法對 \(A[i]\) 作更多的假設,只能\(A[i] = 1\),而後再去匹配了

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int M = 51000100;
char a[M],s[M << 1]; 
int n,p[M << 1],ans = 1;
//===============================
void pre_(){
    s[0] = s[1] = '#';
	for(int i = 1;i <= n; i++){
		s[i * 2] = a[i];
		s[i * 2 + 1] = '#';
	}	
  n = n * 2 + 1;
}
//===============================
void Mana_(){
	int mx = 0,id;
	for(int i = 1;i < n; i++){
		if(i < mx)//在範圍內manacher精髓 
		p[i] = min(p[(id << 1) - i],p[id] + id - i);//前兩種狀況 
		else 
		p[i] = 1;
	   while(s[i + p[i]] == s[i - p[i]])p[i]++;//繼續擴展p[i]長度 
	   if(i + p[i] > mx){
	   	  mx = i + p[i];//更新mx,id值
		  id = i;
	   }
    }
}
int main(){
	scanf("%s", a + 1);
	n = strlen(a + 1);
	pre_();
    Mana_();
	for(int i = 0;i <= n * 2 + 1; i++)
	        ans = max(ans, p[i]);
    printf("%d", ans - 1);
}
相關文章
相關標籤/搜索