【马拉车算法】POJ 3974 Palindrome

本文深入解析Manacher算法,一种高效求解最长回文子串问题的算法,详细介绍了其核心思想与实现细节,并附带AC代码,适用于处理大规模数据集。

这段时间要沉迷刷题一段时间了,就让优快云陪我一起吧!

一、题目大意

题目的意思很简单,就是给定字符串的最长回文子串。

二、题目思路以及AC代码

虽然题目意思很容易理解,也是一个经典的DP题目,但这道题却并不好AC,主要是给的数据量太大了,字符串的长度最长为1000000。

如果是最经典的最长回文子串的做法就是区间DP,时间复杂度是n2,一看时间,给了15s,好像还有戏,那么我们来看一下空间复杂度,对于经典的区间DP,也是n2,这就没办法了,1000G的内存,这肯定是不行的。

所以这道题肯定要用O(n)的方法来解。那么问题是如何将最长回文子串用O(n)的方法求解呢?这也是我先学的,名为Manacher算法,俗称马拉车算法。其实代码中最为主要的就是一个公式,其他的部分可以参照Manacher’s Algorithm(很短,大家不会花很长时间)。

对于其中的那个p[i]的公式,我觉得他讲述的还不够清楚,当然,这里也是为自己巩固一下。公式是这样的:
在这里插入图片描述
具体的代码实现部分是如下这样:

for (int i = 1; i < len; i++) {
		p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
		while (t[i + p[i]] == t[i - p[i]]) p[i]++;
		if (mx < i + p[i]) {
			mx = i + p[i];
			id = i;
		}
		if (resLen < p[i]) {
			resLen = p[i];
		}
	}

我觉得这段还是结合代码来讲解比较好,代码中的t就是形如’$#a#b#c…#b#'的字符串,对于上述公式,可以看到,首先比较mx和i,为什么要比较这两个呢?因为我们的目的是要实现一个时间复杂度为O(n)的算法,所以就尽可能的不重复的对一些值进行遍历,那么这里mx表示当前找到的回文串中最右边的那个值的索引,i表示当前遍历到的值,如果mx>i,则说明以i为中心的回文子串已经有一部分是知道的,其实就是跟i关于id对称的位置的p,设这个对称位置为j,那么就是p[j],但这里的p[j]可能会超过mx-i,那样就不能保证一定是回文子串了,就需要自行去遍历检验。这样说可能比较抽象,我通过实例来说明一下。

比如现在有字符串$#a#b#a#c#a#b#a#,那么当我扫描到i = 12(b)的时候,我此时的id = 8©,mx = 16,那么我首先比较mx和i,mx > i,说明新扫描的b被包含在之前的某个回文串中,也就是id - p[id]到id + p[id]中,那么说明我此时的回文串和与c对称的位置的回文串部分相同,所以我找到j = 2*id - i = 4的位置(左边的b),然后看他的p[j]是多少,发现是4,而且mx - i也是4,所以直接让p[i] = 4,就可以免去查找这几个地方的遍历了,就减少了时间复杂度。

下面给出AC代码,500ms

#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>
#define MAXN 2000010
using namespace std;

int p[MAXN];
string str;

int Manacher(string s) {
	int len = s.length();

	string t = "$#";
	for (int i = 0; i < len; i++) {
		t += s[i];
		t += '#';
	}

	int mx = 0;
	int id = 0;
	int resLen = 0;
	len = t.length();
	for (int i = 1; i < len; i++) {
		p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
		while (t[i + p[i]] == t[i - p[i]]) p[i]++;
		if (mx < i + p[i]) {
			mx = i + p[i];
			id = i;
		}
		if (resLen < p[i]) {
			resLen = p[i];
		}
	}

	return resLen - 1;
}

int main()
{
	int Case = 1;
	while (cin >> str) {
		if (str == "END") break;

		cout << "Case " << Case++ << ": ";
		cout << Manacher(str) << endl;
	}

    return 0;
}

如果有问题,欢迎大家指正!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值