Manacher 和 KMP 學習筆記
阿新 • • 發佈:2021-10-31
給同級神仙們講的演算法。
和 \(\text{aba}\) ,一種是偶迴文,另一種是奇迴文。
和 \(j\) 來更新到我們已知的可以更新的最大長度。
\(\text{Manacher}\)
解決問題:求一個字串中最長迴文連續子序列的演算法。
首先我們考慮一個基礎的暴力:列舉l和r,對於每個l和r求遍歷一遍判斷是否為迴文,複雜度 \(O(N^3)\) 。
在這個基礎上稍微優化一下,也是很顯然的做法:長度為奇數迴文串以最中間字元的位置為對稱軸左右對稱,而長度為偶數的迴文串的對稱軸在中間兩個字元之間的空隙。可以遍歷這些對稱軸,在每個對稱軸上同時向左和向右擴充套件,直到左右兩邊的字元不同或者到達邊界。
但這也是 \(O(N^2)\) 的。
那麼我們需要一個更優秀的解法。
這就引入了馬拉車演算法,可以 \(O(n)\) 實現。
首先我們考慮兩種迴文:\(\text{abba}\)
這樣分開很難處理,因此我們考慮把兩個字串變成 #a#b#b#a# 和 #a#b#a 兩個 奇 迴文。
我們定義一個數組 \(p\) ,\(p_i\) 表示 以 \(i\) 為中心的最長迴文半徑。答案即為 \(p\) 的最大值減一乘二。
現在問題是如何求 \(p\) 。
定義兩個變數mx和id。mx就是以id為中心的最長迴文右邊界,也就是mx=id+p[id]
,隨後我們需要mx做出它的最大貢獻。
假設我們在求 \(p_i\)(以 \(i\)為中心的最長迴文半徑),如果 \(i<mx\)(如上圖),那麼我們就用 \(mx\)
if(i<mx)
p[i]=min(p[2*id-i],mx-i);
\(2 \times id-i\) 是 \(i\) 關於 \(id\) 的對稱點,而 \(p_j\) 表示以 \(j\) 為中心的最長迴文半徑,這樣我們就可以利用 \(p_j\) 和 \(mx\) 加快速度了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; char s[11000002]; char s_new[21000002];//存新增字元後的字串 int p[21000002]; int Init() {//形成新的字串 int len=strlen(s);//len是輸入字串的長度 s_new[0]='$';//處理邊界,防止越界 s_new[1]='#'; int j=2; for(int i=0;i<len;i++) { s_new[j++]=s[i]; s_new[j++]='#'; } s_new[j]='\0';//處理邊界,防止越界(容易忘記) return j;// 返回s_new的長度 } int Manacher() {//返回最長迴文串 int len=Init();//取得新字串的長度, 完成向s_new的轉換 int max_len=-1;//最長迴文長度 int id; int mx=0; for(int i=1;i<=len;i++) { if(i<mx) p[i]=min(p[2*id-i],mx-i);//上面圖片就是這裡的講解 else p[i]=1; while(s_new[i-p[i]]==s_new[i+p[i]])//不需邊界判斷,因為左有'$',右有'\0'標記; p[i]++;//mx對此迴文中點的貢獻已經結束,現在是正常尋找擴大半徑 if(mx<i+p[i]) {//每走移動一個迴文中點,都要和mx比較,使mx是最大,提高p[i]=min(p[2*id-i],mx-i)效率 id=i;//更新id mx=i+p[i];//更新mx } max_len=max(max_len,p[i]-1); } return max_len; } int main() { scanf("%s",&s); printf("%d",Manacher()); return 0; }