1. 程式人生 > >[Manacher]最長回文子串

[Manacher]最長回文子串

我們 min 對稱 () i++ 相對 查找 它的 最大值

很久沒有寫博客了
啪啪啪
寫一些東西吧

最長回文子串怎麽求呢
首先我們得知道什麽是子串,給你一個長長的串,裏面任意連續的一段就是它的子串,當然一個字符也是子串
接著什麽是回文串呢 不好描述 但是看例子很容易懂:aba 121 1221 1
然後我們有一種很顯然的尋找方法 當然是枚舉中點 然後盡可能的往外擴大回文串的長度 這種算法花費的時間是N(字符串長度)*Mk(可擴展長度)Mk<N

當然這樣的時間並不是很讓人滿意
於是Manacher這個人發明了一種新算法
他是這樣想的
假如我先前已經找到的回文串已經是很大了,比如已經覆蓋了整個字符串那麽長
那我找到它以後 就不要找了啊 根據回文串左右對稱的特性

我們可以利用之前找到的可擴展的右端,以及擴展起始點
如果當前查找的被擴展最右端覆蓋,那麽當前查找的起始點可擴展的最小值就是以擴展起始點為對稱中心相對稱的那個點的擴展值
如果擴展到了邊界,就可以繼續擴展下去直到不能擴展,啪啪啪就結束了

非常精彩 但是有個問題 中點可能是一個字符 也可以是兩個字符
分情況討論?費勁
有一種很機智的方法 往字符串每個字符間摻同一個不屬於這個字符串的字符然後前後各摻一個
顯然假如字符串有n個 新字符串必為2*n+1 也就是只需要討論一種情況了
至於到底要求長度還是求字符 就在求的時候開個最大值記錄的存一下就行了

這個算法的時間花費是4*N(字符串長度)(建一遍,找一遍,長度因為擴大了一倍所以是2N)

下面是個打得很爛的模板

//Manacher算法
string s;
char inser='&';
char str[301000];
int len[301000];
void cvt(int n){
    str[0]=inser;
    for(int i=1;i<=(n<<1);i+=2){
        str[i]=s[i>>1];
        str[i+1]=inser;//cout<<str[i]<<" "<<str[i+1]<<" ";
    }
}
void getLen(int l){
    int p0=0,p=0;len[0]=1;
    int j;
    for(int i=1;i<=l;i++){
        if(i<=p){
            j=(p0<<1)-i;
            if(len[j]<(p-i))len[i]=len[j];
            else len[i]=p-i;
//          len[i]=min(len[2*p0-i],p-i);
        }
        else
            len[i]=1;
        int cl=i-len[i],cr=i+len[i],temp=len[i];
        while(cl>=0&&cr<=l&&str[cl]==str[cr]){
        //  if(str[cr]!=inser&&str[cr]>str[cr-2])break;
            temp++;
            cl--;cr++;
        }
        len[i]=temp;
        if((i+len[i]-1)>p){
            p=i+len[i]-1;
            p0=i;
        }
    }
}
int main(){
    while(cin>>s){
        int n;
        n=s.size();
        cvt(n);
        getLen(n<<1);
        int ans=0;
        for(int i=0;i<=(n<<1);i++)
        ans=max(ans,len[i]);
        cout<<ans-1<<endl;
    }
    return 0;
}

[Manacher]最長回文子串