1. 程式人生 > >最長迴文子串的Manacher演算法入門

最長迴文子串的Manacher演算法入門

一.最長迴文子串問題與Manacher演算法.

迴文串,是一種滿足一定條件的字串,設字串S的第i位為 S [ i ] S[i] (字串從1開始),那麼S為迴文串僅當

S [ i ] = S [ S
i + 1 ] \forall S[i]=S[|S|-i+1] .

一個串的最長迴文子串定義為,這個串的一個最長的子串滿足這個子串為迴文串.

求最長迴文子串的樸素演算法是 O

( n 2 ) O(n^2) 的,但是Manacher演算法可以線上性時間複雜度內求解一個串的最長迴文子串.


二.Manacher演算法流程.

我們在看Manacher演算法前,先來看樸素演算法.樸素演算法的思路就是列舉對稱位置,然後從對稱位置開始向兩邊拓展直到失配,時間複雜度為 O ( n 2 ) O(n^2) .

但是我們發現這個樸素演算法的對稱中心有可能是一個字元,也有可能是兩個字元中間的空隙,這樣自己並不好處理.所以我們在所有字元的空隙間插入一個在原串中不會出現的字元,並在開頭加上兩個,末尾加上一個避免邊界判定.

比如下圖中第一個串處理後就變成了第二個串:
在這裡插入圖片描述
處理完之後,我們發現現在對稱中心就只會是在字元上了.並且考慮一個在處理過的串上的一個迴文子串 S [ l . . r ] S[l..r] ,設它的對稱中心為字元 S [ m i d ] S[mid] ,那麼在原串上對應的迴文子串長度就是 S [ m i d . . r ] S[mid..r] 的長度減1.那麼現在的問題就變成了求對於每個mid值,求以它為中心拓展的最長的 S [ m i d . . r ] S[mid..r] 長度,我們設這個值為 p a l [ m i d ] pal[mid] .

接下來的部分與擴充套件KMP比較像,如果有興趣的可以去看看擴充套件KMP.
在這裡插入圖片描述
我們設綠色指標前的所有pal我們都已經處理出來了,紅色線段是一個迴文串,而且以紅色指標為對稱中心,且藍色與綠色指標關於紅色指標對稱.通過迴文串的定義,我們發現一個迴文串翻轉後仍然是迴文串,所以藍色指標的pal值小於或等於綠色指標的pal值.

到這裡我們就可以設計出一個演算法了.與擴充套件KMP類似的,我們記錄一個當前右端點最右邊的紅色線段的對稱中心p.然後在求解i的時候,我們找到它的對應點 p ( i p + 1 ) + 1 = 2 p i 1 p-(i-p+1)+1=2p-i-1 ,分成兩種情況:
1. i > p + p a l [ p ] 1 i>p+pal[p]-1 ,也就是i在右端點最右的紅色線段之外,這時我們發現沒有任何資訊可以使用,所以直接暴力拓展,並更新p.
2. i p + p a l [ p ] 1 i\leq p+pal[p]-1 ,這個時候我們有資訊可以用,也就是先讓 p a l [ i ] = p a l [ 2 p i 1 ] pal[i]=pal[2p-i-1] .如果還可以往右拓展就暴力拓展,並更新p.

注意到這個演算法只有當一個點沒有被拓展過,才會被拓展一次,所以時間複雜度是 O ( n ) O(n) 的.

程式碼如下:

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