Manacher 算法学习笔记
算法用处:
解决最长回文子串的问题(朴素型)。
算法复杂度
我们不妨先看看其他暴力解法的复杂度:
- \(O(n^3)\)
枚举子串的左右边界,然后再暴力判断是否回文,对答案取 \(max\) 。 - \(O(n^2)\)
枚举回文子串的对称轴,向两边扩展,对答案取 \(max\) 。 - \(O(n)\)
\(\texttt{Manacher}\) 算法。
显然我们的 \(\texttt{Manacher}\) 是十分优秀的。。。
实现原理
\(\text{step 1}\)
首先我们需解决一个问题:
回文串的长度有有奇也有偶,这会对我们的算法带来不利,如何解决?
我们可以对原串进行一些改造:
我们在原串的每两个字符之间都插入同一个字符(保证与原来串中的字符不重复)。
举个例子:
对于一个串:\(S = \texttt{"abbabaab"}\)
我们进行上述操作:\(S^\prime = \texttt{"/a/b/b/a/b/a/a/b/"}\)
我们把它变成这样,于是我们发现:
对于长度为奇数的回文串,它的回文中心是原串的字符;
对于长度为偶数的回文串,它的回文中心是加入的字符;
那么我们 \(S\) 中的任意一个回文子串都会在 \(S^\prime\) 中唯一对应一个字符。
于是我们可以开始在 \(S^\prime\) 上进行操作。
\(\text{step 2}\)
我们对于 \(S^\prime\) 中的每一个字符 \(S^\prime[i]\) 维护一个 \(f[i]\)
表示以 \(S^\prime[i]\) 为回文中心可以扩展的最远位置和 \(i\) 的下标之差(也就是这两点之间的距离)
我们只要求出了每一个位置的 \(f[i]\),那么该位置作为对称中心时的回文串的长度就是 \(f[i]\):
- 对于 \(S^\prime[i] \in S\),(比如
/q/w/q/
),显而易见。。。 - 对于 \(S^\prime[i] \notin S\),(比如
/y/y/
),这也是显而易见的。。。
于是我们考虑求出这个 \(f[i]\),答案就是 \(\max\limits_{1\leq i \leq N}\{f[i]\}\)
\(\text{step 3}\)
我们需要发现一个很重要的性质:
- 如果对于一个回文子串,其内部还有一个回文子串,
- 那么也必定会有一个与之全等的字串与它关于这个较大回文子串的对称中心对称
这个性质应该是显然的。。。
在整个算法进行过程中,我们记录一个 \(MaxRight\) 和一个 \(Mid\)
其中 \(MaxRight\) 表示我们扩展过的最远的位置,\(Mid\) 表示它对应的回文中心。
那么对于我们当前的 \(i\) :
- 若 \(i<MaxRight\) ,那么我们可以把 \(f[i]\) 初始化为 \(\min\{f[j], MaxRight-i\}\)
其中的 \(j\) 表示我们刚刚提到过的,该位置对称的那个点,
即以 \(i\) 为对称中心的回文子串与以 \(j\) 为对称中心的回文子串关于 \(Mid\) 对称。 - 若 \(i>MaxRight\) ,那么就不管。。。
然后,我们暴力的去扩展 \(f[i]\),并最终用固定下来的 \(i\) 和 \(f[i]\) 来更新 \(MaxRight\) 和 \(Mid\)。
这样就可以在 \(O(n)\) 时间内求出所有的 \(f[i]\) 了。(复杂度证明?我好像不会。。。)
模板题
Luogu
板子题不解释
/*--------------------------------
--Author: The Ace Bee-------------
--Blog: www.cnblogs.com/zsbzsb----
--This code is made by The Ace Bee
--------------------------------*/
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cctype>
#include <cmath>
#include <ctime>
#define rg register
#define clr(x, y) memset(x, y, sizeof x)
using namespace std;
template < typename T > inline void read(T& s) {
s = 0; int f = 0; char c = getchar();
while (!isdigit(c)) f |= (c == '-'), c = getchar();
while (isdigit(c)) s = s * 10 + (c ^ 48), c = getchar();
s = f ? -s : s;
}
const int _(11000010);
int n, f[_ << 1];
char s[_ << 1], tmp[_];
inline int manacher() {
int n = strlen(s + 1);
for (rg int i = 1; i <= n; ++i) tmp[i] = s[i];
for (rg int i = 1, j = 0; i <= n; ++i) s[++j] = '`', s[++j] = tmp[i];
s[0] = s[n * 2 + 1] = '`', n = n * 2 + 1;
int mx = 0, mid;//mx 即 MaxRight,mid 即 Mid
for (rg int i = 1; i <= n; ++i) {
f[i] = i < mx ? min(mx - i, f[mid * 2 - i]) : 0;
while (s[i - f[i] - 1] == s[i + f[i] + 1]) ++f[i];
if (i + f[i] > mx) mx = i + f[i], mid = i;
}
int res = 0;
for (rg int i = 1; i <= n; ++i) res = max(res, f[i]);
return res;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.in", "r", stdin);
#endif
scanf("%s", s + 1);
printf("%d\n", manacher());
return 0;
}