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