和KMP一样,是个字符串算法,是个O(n)算法,同样也是个通过减少无意义匹配来降低复杂度的算法。
但哪里不一样呢?
哪儿都不一样。
解决的问题不一样,优化的方法和思路不一样,代码不一样,还有这个比KMP简单多了。
它解决的问题是这样的:给出一个字符串,求它以任意位置为中心的最长回文子串长度。这个任意位置可能是某个字符上(比如abcba中的c),也可能在两个字符中间(比如abba中间的两个b)。
暴力就是枚举每个位置并向两边扩展,复杂度
O(n2)
O
(
n
2
)
。
manacher算法利用回文串的性质进行优化:
首先对字符串进行预处理,我们再每两个字符中间和字符串两边各添加一个#,这样的话,像abba这样的字符串就变成了#a#b#b#a#,也就是说,所有的原来长度为偶数的回文子串都变成了奇数长度,这个回文串的中心也从原来的虚无变成现在的#。
其次,维护两个值mid, maxright,maxright代表从左往右扫的过程中遇到最靠右的回文串的最右边的位置,mid代表该回文串的中心。这有什么用呢?
假设我们当前要求以第i个字符为中心的最长回文串,r[i]为这个回文串向右扩展的长度,那么有mid < i <= maxright + 1(想一想,为什么),且所有r[j]已知(j < i)。
当i < maxright时,设j为i关于mid对称的位置,那么有
r[i]=min(maxright−i,r[j])
r
[
i
]
=
m
i
n
(
m
a
x
r
i
g
h
t
−
i
,
r
[
j
]
)
(还是想一想,为什么),然后再向右边进行拓展,如果最右边已经超过了maxright,就更新maxright和mid。
当i >= maxright时,因为右边的一切都是未知的,所以直接去拓展然后更新就好了。
Code
#include <iostream>
#include <cstring>
#include <cstdio>
const int maxn = 1.1e7 + 7; //洛谷奇葩数据范围
using namespace std;
int ans;
char t[maxn];
char s[maxn << 1];
int r[maxn << 1];
inline void manacher(char *t, int *r)
{
int n = strlen(t), mid = 0, maxr = 0;
for (int i = 0; i < n; i++) s[i << 1] = '#', s[i << 1 | 1] = t[i]; //添井号,当然其他符号也可以
s[n << 1] = '#';
for (int i = 0; i <= n << 1; i++) {
if (i < maxr) r[i] = min(r[(mid << 1) - i], maxr - i); //j就是mid * 2 - i
while (i + r[i] + 1 <= n << 1 && ~(i - r[i] - 1) && s[i + r[i] + 1] == s[i - r[i] - 1]) r[i]++; //向右拓展
ans = max(ans, r[i]);
if (i + r[i] > maxr) { //更新
mid = i;
maxr = i + r[i];
}
}
}
int main(void)
{
scanf("%s", t);
manacher(t, r);
cout << ans;
return 0;
}