學習筆記——manacher演算法
前言
$manacher$演算法,這個被$OIer$戲稱為馬拉車的演算法,作為字串入門演算法,非常值得$OIer$學習,並且學會其核心思想--不斷利用之前以求的值來更新之後待求的值。掌握好它,我們就可以開啟$OI$字串演算法的大門。
一、manacher演算法的目的(解決神馬問題)
$manacher$演算法是用於求最長迴文子串長度的演算法。
什麼迴文串?顧名思義,迴文串就是一個字串順序讀和倒序讀都一樣。
舉個栗子:$aba$ $aaaaaa$ $IOI$
二、manacher演算法的基本思路
首先,我們來看到問題:給定一個字串,求其最長迴文子串長度。
$1$.我們來看一看最暴力的解法:列舉所有子串,再分別判斷該串是否是迴文串。時間複雜度$O(n^3)$。暴力不愧是暴力,這時間複雜度直接上天
$2$.既然暴力不行,那我們稍微優化一下。我們可以列舉每一個點作為對稱點(就是該回文子串的對稱中心),再暴力掃描求出最長迴文子串長度。時間複雜度$O(n^2)$。
$3$.改進後還是有點慢,我們可不可以再快一點?這是,我們今天的主角--manacher演算法就閃亮登場了。
我們可以沿著$2$的思路,列舉對稱點,並令$p[i]$為該對稱點所能延伸的最長迴文半徑(從對稱點到該回文子串的左端點或右端點(包含端點和對稱點))。
這時,我們遇到了第一個問題--如果是偶迴文串,那我們該怎麼辦? 我們可以在所有字元之間插入一個無關字元,讓偶串變成奇串。 舉個栗子:aba->@#a#b#a#! (前後兩個端點插入不同符號防越界) abba->@#a#b#b#a#! 這樣,我們就可以愉快的列舉對稱點了。
之後,我們可以得到一個顯然才怪的結論--迴文串長度==$p[i]-1$。
為了防止我們看的雲裡霧裡,還是補充一個解釋比較好:
以abba為例,處理後變成@#a#b#b#a#! 那麼最中間的#號的$p[i]$值為$5$,$5-1=4$,從數值上看,它們是相等的。
那有沒有什麼證明呢?當然要有,不然怎麼用這個演算法。
新串相當於在原串的基礎上$\times 2+1$,我們取它的迴文半徑就可以抵消掉$\times 2$的效果,再$-1$就可以得到真正的迴文串長度。
說了這麼多,提升效率的呢?
我們定義$mx$表示當前找到的最長迴文子串的最右端點的下標,$id$表示$mx$的對稱中心。用$i$表示當前要匹配的節點。
1.當$i<mx$時
利用迴文串的性質,我們可以得到$i$關於$id$對稱的點$j=(id<<1)-i$。
(1)當$p[j]<j$到$id$左端點的距離,如下圖
顯然,$p[i]=p[j]$,且$p[i]$不可以再向兩邊擴充套件。
(2)當$p[j]==j$到$id$左端點的距離,如下圖
顯然,$p[i]=p[j]$,且$p[i]$可以再向兩邊擴充套件。
(3)當$p[j]>j$到$id$左端點的距離,如下圖
此時$p[i]=mx-i$,且$p[i]$不可以再向兩邊擴充套件。
因為如果可以擴充套件,則$cd$,又因為$ab$且$bc$,所以$ad$,那麼$mx$就可以再拓展一位,產生矛盾,捨去。
2.當$i>mx$時
直接$p[i]=1$,之後再判斷是否可以拓展。(因為之前推出的$p[i]$值無法更新當前要求的值)
好了,直接上核心程式碼
for(R int i=1;i<len;++i) {//len是新陣列長度
if(i<mx) p[i]=min(p[(id<<1)-i],mx-i);//同1
else p[i]=1;//同2
while(str[i+p[i]]==str[i-p[i]]) ++p[i];//判斷是否可以再向兩邊拓展
if(p[i]+i>mx) {mx=i+p[i];id=i;}//更新
ans=max(ans,p[i]-1);//更新答案
}
參考資料:
老規矩,練手題