1. 复杂度:O(N)
2. 概念:
回文直径:以 i 为中心,两边能够扩展到的
回文半径:以 i 为中心,一边能够扩展到的
1)回文半径数组(p)
记录每个位置的回文半径
2)最右回文右边界(mx)
表示到目前为止在向右扩展时,能到达的最右边的位置。初始为-1
3)回文右边界的中心(id)
表示第一次到达此右边界的位置
3. 马拉车算法的扩展情况
有两种可能性,第2种可能性分了3种情况
假设从 i 位置开始扩展
可能性1:i不在回文右边界mx内
1)i不在回文右边界里面
复杂度:O(N)
此时直接暴力扩展
可能性2:i在回文右边界 mx 内,包括以下三种情况
2)i在回文右边界里面,i’的回文半径在回文左边界 L 里面
复杂度:O(1)
此时i位置的回文区域不用扩展,i的回文半径与 i’ 一样
3)i在回文右边界里面,i’的回文半径在回文左边界 L 外
复杂度:O(1)
此时i位置的回文区域不用扩展,i的回文半径就是 mx - i
4)i在回文右边界里面,i’的回文半径与回文左边界 L 压线
复杂度:O(N)
此时需要试 mx 右边的区域是否能够构成回文
4. 代码
我比较喜欢的一个模板
模板来自一位大佬的博客:https://www.aptx.xin/c-cmanacher.html
#include <bits/stdc++.h>
using namespace std;
const int N = 1e7 + 5;
char s[N], new_s[2 * N]; // 原串和新串
int p[2 * N]; // 新串的回文半径
int init()
{
int len = strlen(s);
new_s[0] = '$';
new_s[1] = '#';
int j = 2;
for(int i = 0; i < len; i++) {
new_s[j++] = s[i];
new_s[j++] = '#';
}
new_s[j] = '\0';
return j;
}
int Manacher()
{
int len = init(); // 新串的长度
int max_len = -1; // 最长回文的长度
int id = 0, mx = 0; // id 为回文中心,mx 为回文右边界
for (int i = 1; i < len; i++) {
// 在回文右边界内
if (i < mx)
p[i] = min(p[2 * id - i], mx - i); // 2 * id - i是 i 关于 id 的对称点
else
p[i] = 1;
// 向两边扩展
while (new_s[i - p[i]] == new_s[i + p[i]])
p[i]++;
// 更新 id 和 mx
if (mx < i + p[i]) {
id = i;
mx = i + p[i];
}
max_len = max(max_len, p[i] - 1);
}
return max_len;
}
int main(void)
{
scanf("%s", s);
init();
printf("%d\n", Manacher());
return 0;
}
5.一道例题
给定一个字符串,在这个字符串的末尾添加最少的字符使其成为回文字符串。
如果要用Manacher来做的话,需要在最大回文右边界第一次到达字符串长度时,利用回文中心和回文半径来得到回文左边界,再将回文左边界左侧的字符逆序输出即可。
这道题好像还可以用DP来做,但是本菜鸡不会 QAQ
#include <bits/stdc++.h>
using namespace std;
const int N = 1e7 + 5;
char s[N], new_s[2 * N]; // 原串和新串
int p[2 * N]; // 新串的回文半径
int init()
{
int len = strlen(s);
new_s[0] = '$';
new_s[1] = '#';
int j = 2;
for(int i = 0; i < len; i++) {
new_s[j++] = s[i];
new_s[j++] = '#';
}
new_s[j] = '\0';
return j;
}
int Manacher()
{
int len = init(); // 新串的长度
int max_len = -1; // 最长回文的长度
int id = 0, mx = 0; // id 为回文中心,mx 为回文右边界
for (int i = 1; i < len; i++) {
// 在回文右边界内
if (i < mx)
p[i] = min(p[2 * id - i], mx - i); // 2 * id - i是 i 关于 id 的对称点
else
p[i] = 1;
// 向两边扩展
while (new_s[i - p[i]] == new_s[i + p[i]])
p[i]++;
// 更新 id 和 mx
if (mx < i + p[i]) {
id = i;
mx = i + p[i];
}
max_len = max(max_len, p[i] - 1);
// 回文右边界首次到达字符串末尾
if (mx == len) {
int st = 2 * id - mx;
for (int j = st; j >= 0; j--) {
if (new_s[j] >= 'a' && new_s[j] <= 'z') {
cout << new_s[j];
}
}
cout << endl;
break;
}
}
return max_len;
}
int main(void)
{
scanf("%s", s);
init();
Manacher();
return 0;
}