Manacher
一、背景
1975年,Manacher發明了Manacher算法(中文名:馬拉車算法),是一個可以在O(n)的復雜度中返回字符串s中最長回文子串長度的算法,十分巧妙。
讓我們舉個栗子,栗子:
1.字符串:abbababa 最長回文子串:5(abbababa)
2.字符串:abcbbabbc 最長回文子串:7(abcbbabbc)
3.字符串:abccbaba 最長回文子串:6(abccbaba)
傳統方法是,遍歷每個字符,以該字符為中心向兩邊查找。時間復雜度為O(n^2),效率很差;
而這個神奇的Manacher算法將復雜度提升到了O(n)。
來一起瞅一瞅它是如何工作的吧。
二、算法過程分析
回文分為奇回文(ababa)和偶回文(abba),這里比較難以處理,我們使用一個小(sao)技(cao)巧(zuo)(很重要)。我們將字符串首尾和每個字符間插入一個字符(注意:這個自符在串中並未出現)例如:'#'.
栗子!栗子!s='abbadcacda'先轉化成s_new='$#a#b#b#a#d#c#a#c#d#a#\0'('$'與'\0',是邊界,下面的代碼中可以看到)
這樣原串中的偶回文(abba)與奇回文(adcacda),變成了(#a#d#d#a#)與(#a#d#c#a#c#d#a#)兩個奇回文。
定義數組p[],用p[i]表示以i為中心的最長回文半徑。栗子在這里:
那,p[i]該如何求呢?很顯然,p[i]-1正好就是原字符中的最長回文串長度了。
讓我們一起找到正解。
請看下圖:
定義兩個變量mx和id。mx就是以id為中心的最長回文右邊界,也就是mx=id+p[id],隨后我們需要mx做出它的最大貢獻。
假設我們在求p[i](以i為中心的最長回文半徑),如果i<mx(如上圖),那么我們就用mx和j來更新到我們已知的可以更新的最大長度,代碼如下:
if(i<mx) p[i]=min(p[2*id-i],mx-i);
2*id-i是i關於id的對稱點(上圖j)(證明:i-id=id-j),而p[j]表示以j為中心的最長回文半徑,這樣我們就可以利用p[j]和mx加快速度了。
為什么要用p[j]和mx-i取min來更新,什么鬼?
淡定,淡定。我們想一下,p[j](以j為中心的最長回文半徑)是已經知道了(因為是從前面掃過來的),若是p[j]>mx-i,我們是可以知道以j為中心,以mx的對稱點到j的距離為半徑形成的回文字符串是肯定存在的,並且id的左邊直到mx的對稱點與id的右邊 直到mx是一一對應的,不難理解mx是i目前可以更新到的最大回文半徑;若p[j]<mx-i,證明j的回文半徑不到mx的對稱點到j的距離,再次通過(id的左邊直到mx的對稱點與id的右邊 直到mx是一一對應的),不難想到p[i]=p[j]。
取完min就是最大的回文半徑嗎?
顯然不是,接下來的暴力往后掃就好了(學oi的都有暴力傾向)。
三、代碼
#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; }
四、復雜度
完結撒花(復雜度不會證明呀,因為我是蒟蒻)