hihoCoder #1032 : 最长回文子串

时间限制: 1000ms
单点时限: 1000ms
内存限制: 64MB

描述

   小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进。

   这一天,他们遇到了一连串的字符串,于是小Hi就向小Ho提出了那个经典的问题:“小Ho,你能不能分别在这些字符串中找到它们每一个的最长回文子串呢?”

   小Ho奇怪的问道:“什么叫做最长回文子串呢?”

   小Hi回答道:“一个字符串中连续的一段就是这个字符串的子串,而回文串指的是12421这种从前往后读和从后往前读一模一样的字符串,所以最长回文子串的意思就是这个字符串中最长的身为回文串的子串啦~”

   小Ho道:“原来如此!那么我该怎么得到这些字符串呢?我又应该怎么告诉你我所计算出的最长回文子串呢?

   小Hi笑着说道:“这个很容易啦,你只需要写一个程序,先从标准输入读取一个整数N(N<=30),代表我给你的字符串的个数,然后接下来的就是我要给你的那N个字符串(字符串长度<=10^6)啦。而你要告诉我你的答案的话,只要将你计算出的最长回文子串的长度按照我给你的顺序依次输出到标准输出就可以了!你看这就是一个例子。”

提示一  提示二  提示三  提示四
样例输入
3
abababa
aaaabaa
acacdas
样例输出
7
5
3

这一题虽然被打上了“后缀树”的标签,但实际上提示里给出的是Manacher算法,有些人称之为“马拉车”算法。关于这个算法,我也是看了别人的博客学习的。这里我不再过多讲这个算法,只是简单谈一下我自己的理解。

假设现在要判断以i为中心的回文字符长度,此时前面有一个以po为中心的回文子串,它的最右端在mx位置,并且和其它的回文子串相比,它的mx是最右的。现在要分几种情况:

  • 假如i在mx的右边,这个时候没什么技巧,以i为中心一个一个比较吧。
  • 假如mx在i的右边,那么i关于po的对称点 2*po-i 的回文长度可能就是i的回文长度,但是如果i取这段长度可能会导致以i为中文的回文串的最右端超过mx,所以应该取  P[2*po-i] 和 mx-i 的最小值。然后对于mx右边的字符,由于还没有比较过,所以要继续一个一个地比较。

关于最后的结果,假设算出来的是 P[i]=a ,完整的长度则是 a+(a-1)=2a-1,这是转换之后的长度,所以对应的原始的字符长度为 a-1。另外对于插入的特殊符号,可以取# @ $ 之类的,不过取 '\t' '\n' 这样的符号可以让程序更有通用性。下面是我的代码:

#include <iostream>
#include <string>
using namespace std;

int main()
{
	int N;
	cin >> N;
	string str;
	for (int n = 0; n < N; ++n)
	{
		cin >> str;
		// transform string
		int len = str.length() * 2 + 1;
		char* T = new char [len];
		T[0] = '\t';
		for (int i = 1; i < len; i += 2)
		{
			T[i] = str[i/2];
			T[i+1] = '\t';
		}

		// Manacher algorithm
		int mx = 0, ans = 0, po = 0;
		int* P = new int [len];
		P[0] = 0;
		for (int i = 1; i < len - 1; ++i)
		{
			if (mx > i)
				P[i] = min( P[2*po-i], mx-i );
			else
				P[i] = 1;
			while (i - P[i] >= 0 && i + P[i] < len && T[i-P[i]] == T[i+P[i]])
				++P[i];
			if (i + P[i] > mx)
			{
				mx = i + P[i];
				po = i;
			}
			if (P[i] > P[ans])
				ans = i;
		}
		cout << P[ans] - 1 << endl;

		delete [] T;
		delete [] P;
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值