1. 程式人生 > >【3068 HDU】最長迴文串

【3068 HDU】最長迴文串

題目:點選開啟題目連結

思路:這題要用到迴文串匹配的知識點。我們之前遇到這種題傳統思想就是分奇數和偶數情況進行暴力,從前往後遍歷每一個字元,然後以該字元為中心向兩邊查詢,但這樣的時間複雜度很高,是O(n^2),提交的話,肯定會wa。這裡介紹一種新的演算法,Manacher演算法。

Manacher演算法的時間複雜度是O(n),它主要應用於求一個字串中的最長迴文子串的問題。

首先Manacher演算法有一個好處就是不用分奇數和偶數的情況。具體操作就是在每個字元之間都加一個#,將其構成一個新的偶數字符串,例如偶數字符串aaabbc,填充成一個新的字串後變成“@#a#a#a#b#b#c#”;奇數字符串aabbc,填充完後形成的字串就是“@#a#a#b#b#c#”,(這裡的@為了防止越界)也就是說不管之前的字串個數是奇數還是偶數,填充完之後字串個數都是偶數個。

然後就是Manacher演算法有一個核心就是求p[i],p[i]的含義就是以第i個字元為中心的最長迴文串半徑,將字串中每個字元的p[i]都算出來,然後就可以得到這個字串的最長迴文子串長度就是其中最大的p[i]-1.例如:(假設初始字串為ababc)

因此,字串“ababc”的最長迴文子串的長度是4-1=3.

那麼,問題來了,怎麼求 p[i] 咧 \

如果 i 在以pos為中心的迴文串中(即i < r,r是以pos為中心的迴文串的最右區間),則在迴文串中必有一個 j 和 i 與之對稱,那麼此時p[i] = p[j];但是還有一種可能就是我們不知道r以外的字元是否也有相等的,如果有的話,p[i]就不是單純的= p[j]了,p[i]就是p[j] + 半徑以外相等的部分,所以求p[i]就有了下面2種情況。

綜合上述兩種情況,可得p[i]的計算方法

if(i < r) 
    p[i] = min(p[2*pos-i],r-i);///取其中小的一個,以確保p[i]是正確的,然後再對r以外的字元進行比較
else 
    p[i] = 1;
while(s[i+p[i]] == s[i-p[i]])///判斷半徑外的字元還有沒有相等的,如果有,就更新迴文串的半徑長
    p[i]++;

下面,舉個栗子

此時p[j] = 4,r-i = 2,然後p[i] = r-i = 2.(因為半徑以外的字元沒有相等的了)

此時p[j] = 2,r-i = 2,但是p[i] = 2+2 = 4.(此時半徑外還有2個字元相等,因此p[i]是4)

然後到這Manacher演算法可以算是over咧,還有一點兒是如果 i+p[i] > r 的話,需要更新r的值。

下面可以結合程式碼走一遍。

My  DaiMa:

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
char s[220010],a[110005];///s是填充後的字串
int len,pos,p[220010],ans,r;///p[i]是以i為中心的最長迴文串半徑,r是以pos為中心最長迴文串半徑的最右區間
void addchar()///在每個字元之間填一個#,構成新的字串
{
    s[0] = '@';
    int i = 1, j = 0;
    while(i < 2*len+1)
    {
        s[i++] = '#';
        s[i++] = a[j++];
    }
    s[i++] = '#';
    len = 2*len + 2;
}
int Manacher()
{
    ans = r = pos = 0;///剛開始的pos和r都在第一個字元的地方
    for(int i = 0; i < len; i++)
    {
        if(i < r) p[i] = min(p[2*pos-i],r-i);///取其中小的一個,以確保p[i]是正確的,然後再對r以外的字元進行比較
        else p[i] = 1;
        while(s[i+p[i]] == s[i-p[i]])///判斷半徑外的字元還有沒有相等的,如果有,就更新迴文串的半徑長
            p[i]++;
        if(i+p[i] > r)///比較當前的迴文串長度有沒有超過最右區間,若超過了,則更新最右區間r的值,同時中心點pos也要隨之改變
        {
            r = i+p[i];
            pos = i;
        }
        ans = max(ans,p[i]-1);///取其中最長的迴文串長度
    }
    return ans;
}
int main()
{
    while(~scanf("%s",a))
    {
        len = strlen(a);
        addchar();
        ans = Manacher();
        cout << ans << endl;
    }
}