manacher 算法
manacher 算法
\(\mathcal O(n)\) 求有关回文的字符串算法。
定义 \([l, r]\) 表示字符串下标 \(l\) 到 \(r\) 形成的子串。
流程
- 将原字符串首、尾、中间分别插入奇怪字符(如
#
,目的是计算中心点在两个字符之间的回文串),再在最前端插入另一奇怪字符串(如@
,目的是防止数组越界),eg.abcb
变为@#a#b#c#b#
。 - 对每个位置记录 \(p_i\) 表示在当前字符串以 \(i\) 为回文中心的最大回文串为 \([i - p_i + 1, i + p_i - 1]\)。
- 考虑从前往后做,边做边记录两个值,分别为 \(id, maxright\) 分别表示当前所有回文串中,右端点最大的回文串对应的回文中心以及右端点。显然这个可以边做边维护。
- 考虑如何求 \(p_i\),如果说 \(maxright > i\),那么 \(i\) 关于 \(id\) 对称的 \(j(i + j = 2 \times id \implies j = 2 \times id - i)\) 的信息我们是可以用到的(若 \(i + p_j - 1\) 超出 \(maxright\),则超出的那个部分不可取)。具体来说,如果 \(maxright > i\),\(p_i = \min(maxright - i+1, p_j)\),否则 \(p_i = 0\)。
- 不断扩大 \(p_i\),直到求出 \(p_i\)。
时间复杂度证明
- 若 \(p_i = maxright - i + 1\),则 \(maxright\) 随着 \(p_i\) 增大而增大。
- 若 \(p_i = p_j\),则 \(p_i\) 不会再继续扩展。
- 若 \(p_i = 0\),则 \(maxright\) 随着 \(p_i\) 增大而增大。
故,\(maxright\) 均会随着 \(p_i\) 增大而增大,又考虑到 \(maxright\) 最多增大 \(n\) 次,故时间复杂度为 \(\mathcal O(n)\)。
回文串大小
根据 \(p_i\) 的定义,其实对应到原串上,以 \(i\) 为对称中心的最大的回文串长度为 \(p_i-1\)(可以分情况讨论,这里不展开)。
#include <bits/stdc++.h>
const int N = 1.1e7 + 10;
int len, lenr;
char s[N], r[N * 2];
int p[N * 2], ans;
inline void manacher()
{
int id = 0, maxright = 0;
for (int i = 1; i <= lenr; ++ i)
{
if (maxright > i)
p[i] = std::min(maxright - i + 1, p[id * 2 - i]);
else p[i] = 0;
while (r[i + p[i]] == r[i - p[i]])
p[i] ++;
if (i + p[i] - 1 > maxright)
{
id = i;
maxright = i + p[i] - 1;
}
ans = std::max(ans, (p[i] - 1));
}
}
int main()
{
scanf("%s", s + 1);
len = strlen(s + 1);
r[0] = '@';
for (int i = 1; i <= len; ++ i)
r[i * 2 - 1] = '#', r[i * 2] = s[i];
r[len * 2 + 1] = '#';
lenr = len * 2 + 1;
manacher();
printf("%d\n", ans);
return 0;
}