manacher算法

一、背景

马拉车算法主要用于求字符串的最长回文子串,一般的暴力算法在求回文子串时最
坏的时间复杂度为O(n ^ 2),但manacher的时间复杂度可以优化到O(n)

二、算法前言

首先我们要了解:求最长回文子串时,我们一般都是从回文串的中心点向外枚举,这里就牵扯到字符串长度的问题了,怎么处理长度为奇数和偶数的子串呢?这里有一个小技巧:

我们可以在字符串每两个字符间加上一个不干扰原字符串的字符,如“#”,“@”等

eg.现在有一个长度为奇数的字符串和一个长度为偶数的字符串:

abdcf
jkdj

按照上面的原则,我们就可以将其转化为:a#b#d#c#fj#k#d#j,同时为了避免边界的问题,最后这两个字符串就变为了
#a#b#d#c#f##j#k#d#j#
这样,不管原来的字符串长度为奇为偶,最后的字符串长度一定为奇数。

三、算法详解

首先,我们引入三个元素:

1、p[maxn]:p[i]表示以i为中心点的最长回文子串的半径(即1 / 2个最长回文子串长度)

2、id:在1 ~ i个中心点中能拓展到最右边位置的中心点

3、mx:在1 ~ i个中心点的最长回文子串中所能拓展到的最右边的位置

先用一张图模拟一下manacher的核心部分
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
其中i为当前遍历到的中心节点,现在我们要求p[i],即第i点的最长回文子串长度,首先我们在数轴上找到一个j,j为i关于id的对称点,接下来我们分为两类:

1、i <= mx

在大条件下,i和j都在大回文串中,我们很容易知道j ~ id与id ~ i完全相同。下面继续分类:

1、p[j] <= mx ~ j的距离,即以j为中心点的最长回文子串的左端点在大回文串中,这样我们就可以保证到p[i]至少等于p[j]
2、p[j] > mx ~ j的距离,即以j为中心点的最长回文子串的左端点在大回文串左端点以外,这种情况下,我们就不再能保证p[i]至少等于p[j]了,因为mx以外的字符不匹配,但我们知道mx对称点 ~ j始终与i ~ mx相同,所以这时的p[i]至少等于mx - i

综上,为了不对p[j]再进行判断,所以当i <= mx时,我们用一个min得到了p[i]的最小值:

p[i] = min(p[2 * id - i], mx - i)

2、i > mx

当i > mx时,我们就什么也保证不了,但i自身也是一个回文串,所以此时的式子为

p[i] = 1;

分割线

有了最小的p[i],剩下的就要靠我们自己去暴力拓宽了,同时不要忘记更新mx与id

while(ss[i + p[i]] == ss[i - p[i]]) p[i]++;
if(i + p[i] > mx) {
	mx = i + p[i];
	id = i;
}

四、全代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int p[maxn];
char s[maxn];  
char ss[maxn << 1];
int cnt = 0;
void manacher(char *s) {
	int len = strlen(s);
	ss[cnt++] = '@';	
	ss[cnt++] = '#';	
	for(int i = 0; i < len; i++) {
		ss[cnt++] = s[i];
		ss[cnt++] = '#';
	}
	ss[cnt++] = '&';
	int id = 0, mx = 0;
	for(int i = 0; i < cnt; i++) {
		if(i < mx) p[i] = min(p[2 * id - i], mx - i);
		else p[i] = 1;
		while(ss[i + p[i]] == ss[i - p[i]]) p[i]++;
		if(i + p[i] > mx) {
			mx = i + p[i];
			id = i;
		}
	}
}
int main() {
	cin >> s;
	int ans = -1;
	manacher(s);
	for(int i = 0; i < cnt; i++) {
		ans = max(ans, p[i] - 1);
	}
	cout << ans << "\n";
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值