Manacher(馬拉車)————O(n)回文子串


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;
}

 四、復雜度

 

完結撒花(復雜度不會證明呀,因為我是蒟蒻)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM