問題導入:
現在有一個長度的字符串,現在需要求出這個字符串中的最大回文子串。
算法舉例:
- 最樸素算法,枚舉迴文串的對稱中心,分別先左和向右擴展,依次更新最大值。算法複雜度。
- + 二分:計算字符串的前綴值,枚舉中點,二分迴文字串的長度。算法複雜度。
- 迴文自動機,代碼複雜,思維難度大,算法複雜度。
- ,代碼簡單,思維難度不高,算法複雜度。
顯然,是一種簡單有實惠的算法,現在來解釋一下這個算法。
Manacher算法:
迴文串的對稱中心有奇有偶,判斷起來有點麻煩,這裏引入一個小技巧,在每個字符的左右都插入‘#’,
如,在插入字符後就變成了#### ,可以在字符串的首尾加入個奇怪的字符,以防數組越界。
我們還需要引入輔助數組,表示以爲對稱中心的最長迴文串的半徑長度,例如下圖:
顯然可知,就是這個字符串中的最大回文子串。因此算法的根本目的就是求出上述的所有。
我們使用表示當前的迴文子串的右邊界,表示當前迴文子串的對稱中心,如圖:
如果,爲對於的對稱點,,因爲到的子串等於的對稱點到的子串,所以在的對稱點到的最大回文長度就是到的迴文長度,那麼的初始值就是。
如果,那麼說明在沒有相應的對稱點,那麼的初始值就是。
初始值確定後,就根據初始值向兩邊暴力比較擴展,且注意更新和的值。
Manacher複雜度分析:
如果且,那麼單次求解的時間複雜度爲。
反之,那麼一定迴向外擴展,就會使更新,使之增大,複雜度是的。
綜上所述,算法的複雜度是線性的。
代碼如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 31001000;
int n, m;
int p[N] = {}, tot = 0;
char s[N];
inline void input() {
char ch = getchar();
while (ch >= 'a' && ch <= 'z')
s[++ tot] = ch, s[++ tot] = '#', ch = getchar();
}
int main() {
s[tot] = '$';
s[++ tot] = '#';
input();
int ans = 0;
int mx = 0, id = 0;
// cout<<tot<<' ';
for (int i=1;i<=tot;i++) {
if (i < mx) p[i] = min(p[(id<<1)-i], mx-i);
else p[i] = 1;
while (s[i-p[i]] == s[i+p[i]]) p[i] ++;
if (i + p[i] > mx) mx = i + p[i], id = i;
ans = max(ans, p[i]);
}
// for (int i=0;i<=tot;i++) cout<<p[i]<<' ';
printf("%d\n",ans-1);
return 0;
}