Manacher算法模板

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值