最長迴文子串的Manacher演算法入門
一.最長迴文子串問題與Manacher演算法.
迴文串,是一種滿足一定條件的字串,設字串S的第i位為 (字串從1開始),那麼S為迴文串僅當 .
一個串的最長迴文子串定義為,這個串的一個最長的子串滿足這個子串為迴文串.
求最長迴文子串的樸素演算法是
的,但是Manacher演算法可以線上性時間複雜度內求解一個串的最長迴文子串.
二.Manacher演算法流程.
我們在看Manacher演算法前,先來看樸素演算法.樸素演算法的思路就是列舉對稱位置,然後從對稱位置開始向兩邊拓展直到失配,時間複雜度為 .
但是我們發現這個樸素演算法的對稱中心有可能是一個字元,也有可能是兩個字元中間的空隙,這樣自己並不好處理.所以我們在所有字元的空隙間插入一個在原串中不會出現的字元,並在開頭加上兩個,末尾加上一個避免邊界判定.
比如下圖中第一個串處理後就變成了第二個串:
處理完之後,我們發現現在對稱中心就只會是在字元上了.並且考慮一個在處理過的串上的一個迴文子串
,設它的對稱中心為字元
,那麼在原串上對應的迴文子串長度就是
的長度減1.那麼現在的問題就變成了求對於每個mid值,求以它為中心拓展的最長的
長度,我們設這個值為
.
接下來的部分與擴充套件KMP比較像,如果有興趣的可以去看看擴充套件KMP.
我們設綠色指標前的所有pal我們都已經處理出來了,紅色線段是一個迴文串,而且以紅色指標為對稱中心,且藍色與綠色指標關於紅色指標對稱.通過迴文串的定義,我們發現一個迴文串翻轉後仍然是迴文串,所以藍色指標的pal值小於或等於綠色指標的pal值.
到這裡我們就可以設計出一個演算法了.與擴充套件KMP類似的,我們記錄一個當前右端點最右邊的紅色線段的對稱中心p.然後在求解i的時候,我們找到它的對應點
,分成兩種情況:
1.
,也就是i在右端點最右的紅色線段之外,這時我們發現沒有任何資訊可以使用,所以直接暴力拓展,並更新p.
2.
,這個時候我們有資訊可以用,也就是先讓
.如果還可以往右拓展就暴力拓展,並更新p.
注意到這個演算法只有當一個點沒有被拓展過,才會被拓展一次,所以時間複雜度是 的.
程式碼如下:
int manacher(char *c,int len){
int n,ans=0;
tmp[1]='#';tmp[n=2]='#';
for (int i=1;i<=len;++i)
tmp[++n]=c[i],tmp[++n]='#';
int p=0,t;
for (int i=1;i<=n;++i){
pal[i]=i<=p+pal[i]-1?pal[2*p-i+1]:1;
while (tmp[i+pal[i]]==tmp[i-pal[i]]) ++pal[i];
if (i+pal[i]>p+pal[p]) p=i;
ans=max(ans,pal[i]-1);
}
return ans;
}
三.例題與程式碼.
題目:luogu3805.
程式碼如下(被卡常只有75分):
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=11000000;
char tmp[N*2+9];
int pal[N*2+9];
int manacher(char *c,int len){
int n,ans=0;
tmp[1]='#';tmp[n=2]='#';
for (int i=1;i<=len;++i)
tmp[++n]=c[i],tmp[++n]='#';
int p=0,t;
for (int i=1;i<=n;++i){
pal[i]=i<=p+pal[i]-1?pal[2*p-i+1]:1;
while (tmp[i+pal[i]]==tmp[i-pal[i]]) ++pal[i];
if (i+pal[i]>p+pal[p]) p=i;
ans=max(ans,pal[i]-1);
}
return ans;
}
char c[N+9];
int n;
Abigail into(){
scanf("%s",c+1);
n=strlen(c+1);
}
Abigail outo(){
printf("%d\n",manacher(c,n));
}
int main(){
into();
outo();
return 0;
}