manacher,又叫馬拉車。算法
馬拉車能夠用來求最長迴文子串。數組
思考如何求最長迴文子串,咱們首先會想到一個 \(O(n^3)\) 的解法:枚舉全部子串,而後判斷是否爲迴文串。固然這個解法很是大 蠢。
而後,咱們又會有一個想法,枚舉每一個點,而後從這個點向外拓展,若是相同則拓展,不然記錄答案。這裏有個問題,就是迴文串的長度多是偶數,也就是沒有中間點,那麼咱們能夠在每兩個字符之間添加 '#',這樣就有了中間點了,像這樣:
注意,前面的 '@' 是防止數組越界的,若是掃到頭尾都是迴文串,那麼若是繼續掃下去就會越界,而這裏有一個 '@' 就能夠防止越界的發生。
那麼此時這個迴文串真正的長度應該爲 半徑(包含中間點)-1。
這個算法的時間複雜度是 \(O(n^2)\)的。
咱們思考上面那個算法,發現他有許多位置是重複枚舉的,好比:
橙色線中間部分是一個迴文串,紅色線中間部分也是一個迴文串,圈起來的是迴文串的中心。
能夠發現,這兩個迴文串會有重複的地方,那麼有沒有方法避免這些重複枚舉呢?答案是有的。
首先,咱們記錄一個最大的 \(r\) 和對應的 \(mid\),使得 \(mid - (r - mid)\)~\(r\) 是一個迴文串。(這裏的 \(mid\)其實就是迴文串中心的位置啦)
而後,若咱們當前查詢的節點 \(now\) 在 \(r\) 以內:
那麼,既然在一個迴文串的右半部分(由於是從前日後掃,因此必定在 \(mid\) 右邊,是有半部分),則必定有一個對應的左半部分的字符(下面簡稱 \(lc\) QwQ):
而 \(lc\) 的半徑是已經求出的,若 \(lc\) 的半徑是在這個大回文串以內,那麼 \(now\) 半徑就和 \(lc\) 的同樣。但若是以 \(lc\) 爲中心的字符串不全在大回文串之中呢?好比:
那麼,此時咱們能肯定的半徑就是 \(r-now\),也就是這一段:
那麼,咱們只要在上面兩個中取一個最小值便可。spa
code:code
#include<cstdio> #include<string> using namespace std; string s; int mid,r,ans,b[22000005];//b[i]記錄以i爲中心的迴文串的半徑 int min(int x,int y){return x<y?x:y;} void read(string &ss) { ss="@#"; char ch=getchar(); while(ch<'a'||ch>'z') ch=getchar(); while(ch>='a'&&ch<='z') ss+=ch,ss+='#',ch=getchar(); return ; } int main() { read(s); for(int i=1;i<(int)s.length();i++) { if(i<=r) b[i]=min(b[mid*2-i],r-i+1); while(s[i-b[i]]==s[i+b[i]]) b[i]++; if(i+b[i]-1>r) r=i+b[i]-1,mid=i;//更新r和mid if(b[i]-1>ans) ans=b[i]-1; } printf("%d\n",ans); return 0; }