写在前面
本文不是写给完全没接触过的小白看的,是自己对Manacher算法的一些理解和总结,主要是写给自己看的。
如果你已经学会或者看了其他的教程没明白的,可以来看看咱的这篇文章。
Manacher算法
利用回文字符串的特性,在O(n)的时间复杂度下求取以每个字符为中心的最长回文子串长度的数组。
核心思想
在求以 s[i]
为中心的最长回文子串的过程中,可以直接取 p[i] = p[i_mirror]
。(s[i_mirror]是s[i]关于s[Center]的对称点)
对上面这句话的解释
以回文串 s = "a b a b f b a b a b a"
为例:
整个串以 'f'
为中心有最长回文子串 s[0] ~ s[8] = "a b a b f b a b a"
。
在求p[6]
时,可以直接取p[4] = p[2]
。
因为以s[2] = 'a'
为中心的最长回文子串是 s[1] ~ s[3] = "a b a"
。 s[1] ~ s[3]
在以s[4] = 'f'
为中心的回文子串的左边界,那也就是说在s[4]
的右边一定有s[5] ~ s[7] = "a b a"
,也就是说p[6]
可以直接取p[2]
的值。
几种情况还需要继续拓展
可以看到,以上成立的条件一定是左右两段区间在一个大的回文串的左右两侧。
所以当前位置i
复制了i_mirror
的值之后,还可以继续超过大的回文串的的右边界向两侧拓展的时候,就需要更新当前的大的回文串,使得当前选定的大的回文串包含当前的位置i
,即更新C
和R
的值。
或者当前位置i
直接超出了R的边界,没有任何东西可以参考,则置p[i] = 0
,从0开始拓展。
代码
#include <bits/stdc++.h>
using namespace std;
string pre_process(const string& s) {
string ret("^#");
for (char c : s) {
ret.append(1, c);
ret.append(1, '#');
}
ret.append(1, '$');
return ret;
}
string Manacher(const string& s, const string& raw) {
int len = s.size(), p[2010] = {}, C = 0, R = 0;
for (int i = 1; i < len - 1; i++) {
int i_mirror = 2 * C - i;
if (R > i) {
p[i] = min(R - i, p[i_mirror]); // 1.超出了R or 2.p[i_mirror]遇到了原字符串左边界
} else {
p[i] = 0;
}
while (s[i + p[i] + 1] == s[i - p[i] - 1]) {
p[i]++;
}
if (i + p[i] > R) {
C = i;
R = i + p[i];
}
}
int ct_idx = max_element(p, p + len) - p;
int start = (ct_idx - p[ct_idx]) / 2;
return raw.substr(start, p[ct_idx]);
}
int main() {
string s;
cin >> s;
string ss = pre_process(s);
string palindromic = Manacher(ss, s);
cout << palindromic << endl;
return 0;
}