Manacher 演算法學習筆記
原更新日期:2018-11-01 16:52:35
\(O(n)\)迴文串
Manacher 是什麼
Manacher 是一種可以在\(O(n)\)的時間複雜度內求出一個字串的最長迴文子串的演算法。
Manacher,中文一般念做「馬拉車」。
Manacher Algorithm 的思想
首先我們來看一道題洛谷P3805【模板】manacher演算法。
考慮一下暴力做法,就是列舉字串的邊界並進行驗證,時間複雜度\(O(n^3)\)
考慮一下優化,我們可以列舉所有“迴文子串”的對稱軸(儘管它現在不一定是迴文子串)並向兩邊進行擴充套件,用一個數組external[i]
記錄第i
個字元可向外擴充套件的數量,顯然陣列中最大值的二倍就是答案,時間複雜度均攤\(O(n^2)\)
但這還不夠快……畢竟\(\text{|s|} \leq 11000000\)
於是我們考慮在優化的思想基礎上進行再次優化。
在此之前,我們首先要解決一個棘手的問題——字串的長度。
一個字串子串的對稱軸是在字母中間還是在字母上,是由子串長度為偶數還是奇數決定的。於是,為了統一對於奇數長度字串和偶數長度字串的做法,我們需要對字串進行修改。(程式碼見「程式碼實現」Pre()
部分)
就比如說
- - - - - -
|%|%|%|w|y|h|
我們要用一些無關緊要的字元填一下
- - - - - - - - - - -
|%|!|%|!|%|!|w|!|y|!|h|
這樣更好處理
修改完了之後,就是真正的Manacher()
首先,我們要用一個變數
maxRight
記錄「當前的 最靠右的 迴文子串的 右端點」,和一個變數mid
記錄「當前的 最靠右的 迴文子串的 對稱軸所在的 字元的 下標」,注意這裡的mid
是可以不賦初值的
我們迴圈列舉經過處理的字串的每一個字元。對於每一個字元的下標i
,如果i < maxRight
,那麼我們就可以獲取external[i]
的部分資訊(external[i]
的意義和上文相同),否則就只能將external[i]
設為1
接著就是和暴力一樣的擴充套件了,我這裡選擇用for
語句實現(
最後更新一下maxRight
和mid
即可
最終答案就是external[]
的最大值——而不是2倍,因為這是我們擴充套件過的字串,最終答案還要\(\times \frac{1}{2}\)
Manacher Algorithm 的程式碼實現
同樣也是「manacher模版」的程式碼實現。
/* -- Basic Headers -- */
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
/* -- STL Iterators -- */
#include <vector>
#include <string>
#include <stack>
#include <queue>
/* -- External Headers -- */
/* -- Defined Functions -- */
#define For(a,x,y) for (int a = x; a <= y; ++a)
#define Forw(a,x,y) for (int a = x; a < y; ++a)
#define Bak(a,y,x) for (int a = y; a >= x; --a)
/* -- Defined Words -- */
using namespace std;
namespace FastIO {
inline int getint() {
int s = 0, x = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-') x = -1;
ch = getchar();
}
while (isdigit(ch)) {
s = s * 10 + ch - '0';
ch = getchar();
}
return s * x;
}
inline void __basic_putint(int x) {
if (x < 0) {
x = -x;
putchar('-');
}
if (x >= 10) __basic_putint(x / 10);
putchar(x % 10 + '0');
}
inline void putint(int x, char external) {
__basic_putint(x);
putchar(external);
}
}
namespace Solution {
const int MAXN = 31000000 + 10;
// 沒錯,就是要開這麼大
int n, external[MAXN];
char s[MAXN], str[MAXN << 1];
void Pre() {
str[0] = str[1] = '~';
for (int i = 0; i < n; ++i) {
str[i * 2 + 2] = s[i];
str[i * 2 + 3] = '~';
}
n = n * 2 + 2;
str[n] = 0;
}
void Manacher() {
int maxRight = 0, mid = 0; // mid 初值無所謂
for (int i = 1; i < n; ++i) {
if (i < maxRight) {
external[i] = std::min(external[(mid << 1) - i], external[mid] + mid - i);
} else {
external[i] = 1;
}
for (; str[i + external[i]] == str[i - external[i]]; ++external[i]);
if (external[i] + i > maxRight) {
maxRight = external[i] + i;
mid = i;
}
}
}
void Work() {
cin >> s;
n = (int) strlen(s);
Pre();
Manacher();
int ans = 1;
for (int i = 0; i < n; ++i) ans = std::max(ans, external[i]);
cout << ans - 1 << endl;
}
}
int main(int argc, char *const argv[]) {
#define HANDWER_FILE
#ifndef HANDWER_FILE
freopen("testdata.in", "r", stdin);
freopen("testdata.out", "w", stdout);
#endif
using namespace Solution;
using namespace FastIO;
Work();
return 0;
}