《算法竞赛进阶指南》 第二章 Acwing 139. 回文子串的最大长度

地址 https://www.acwing.com/problem/content/141/

如果一个字符串正着读和倒着读是一样的,则称它是回文的。
给定一个长度为 N 的字符串 S,求他的最长回文子串的长度是多少。

输入格式
输入将包含最多 30 个测试用例,每个测试用例占一行,以最多 1000000 个小写字符的形式给出。
输入以一个以字符串 END 开头的行表示输入终止。

输出格式
对于输入中的每个测试用例,输出测试用例编号和最大回文子串的长度(参考样例格式)。
每个输出占一行。

输入样例:
abcbabcbabcba
abacacbaaaab
END
输出样例:
Case 1: 13
Case 2: 6

解答

  1. 前置知识 字符串哈希
    对于一个长度为len的字符串str 可以使用一个数组 unsigned long long hashV[N] 来记录
    1~n的 字符串的哈希值 hashV[i]=hashV[i-1]x131 + str[i]-'a'+1;
    对于a到b的区间字符串的哈希值就是 hashV[b] - hashV[a]x131^(b-a+1)

那么计算出字符串的正序和逆序哈希值
以每个点展开向两旁扩展 获取哈希值 如果每个点两边的哈希值一致 那么它就是回文串记录回文串长度,返回最大值即可.

  1. 回文判断有aba和aabb两种,中间填充符号就可以直接划分为一种判断方式了
    比如转化为 a#b#a a#a#b#b

  2. 考虑到数据量较大 判断回文串的长度使用二分比遍历要快,否则会tle

// Ac139.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <algorithm>
#include <memory.h>


using namespace std;


/*
如果一个字符串正着读和倒着读是一样的,则称它是回文的。

给定一个长度为 N 的字符串 S,求他的最长回文子串的长度是多少。

输入格式
输入将包含最多 30 个测试用例,每个测试用例占一行,以最多 1000000 个小写字符的形式给出。

输入以一个以字符串 END 开头的行表示输入终止。

输出格式
对于输入中的每个测试用例,输出测试用例编号和最大回文子串的长度(参考样例格式)。

每个输出占一行。

输入样例:
abcbabcbabcba
abacacbaaaab
END
输出样例:
Case 1: 13
Case 2: 6
*/


const int N = 2001000;
//const int N = 1001000;
unsigned long long lh[N];
unsigned long long rh[N];
unsigned long long p[N];
const int base = 131;
char str[N];
char cpStr[N];
int ans = 1;


unsigned long long GetHashVl(unsigned long long h[N], int l, int r)
{
	return h[r] - h[l - 1] * p[r - l + 1];
}

unsigned long long GetHashVr(unsigned long long h[N], int l, int r)
{
	return h[l] - h[r + 1] * p[r - l + 1];
}

int GetRealLen(int len, char c) {
	if (c == '#') {
		if (len % 2 == 0) return len;
		else return (len / 2 + 1) * 2;
	}
	else {
		if (len % 2 == 0) return len + 1;
		else return (len / 2) * 2 + 1;
	}
}

int main()
{
	int idx = 1;
	while (1) {
		ans = 1;
		memset(cpStr, 0, sizeof(cpStr));
		memset(lh, 0, sizeof lh);
		memset(rh, 0, sizeof lh);

 
		scanf("%s", str + 1);
		if (strcmp(str + 1, "END") == 0) { break; }
		int len = strlen(str + 1);
		memset(cpStr, 0, sizeof(cpStr));
		for (int i = 0; i < len; i++) {
			cpStr[i * 2 + 1] = str[i + 1];
			cpStr[i * 2 + 2] = '#';
		}
        
		//计算正向逆向的哈希值的 然后遍历字节得到以遍历点为中心的最长回文长度
		len = strlen(cpStr + 1) - 1;
		cpStr[len + 1] = '\0';
		p[0] = 1;
		for (int i = 1; i <= len; i++) {
			lh[i] = lh[i - 1] * base + cpStr[i] - 'a' + 1;
			int j = len - i + 1;
			rh[j] = rh[j + 1] * base + cpStr[j] - 'a' + 1;
			p[i] = p[i - 1] * base;
		}

		for (int i = 1; i <= len; i++) {
			int r = min(i - 1, len - i);
			int l = 0;

			while (l < r) {
				int mid = (l + r+1) >> 1;
				if (GetHashVl(lh, i - mid, i) != GetHashVr(rh, i, i + mid)) {
					r = mid-1;
				}
				else {
					l = mid ;
				}
			}

			int realLen = GetRealLen(l, cpStr[i]);
			ans = max(ans, realLen);
		}

		printf("Case %d: %d\n", idx++, ans);
	}


	return 0;
}

第二种方法 manacher代码

#include <iostream>
#include <string.h>

using namespace std;
int n, m, p[2000010];
char s[1000010], t[2000010];

int manacher() {
	memset(t,0,sizeof t);
	memset(p,0,sizeof p);
	n = strlen(s + 1);m = 0;
	t[++m] = '#';
	for (int i = 1; i <= n; i++) {t[++m] = s[i]; t[++m] = '#';}
	int M = 0, R = 0;
	for (int i = 1; i <= m; i++) {
		if (i > R)
			p[i] = 1;
		else
			p[i] = min(p[2 * M - i], R - i + 1);
		while (p[i] <i && i+p[i] <= m && t[i - p[i]] == t[i + p[i]]) 
			++p[i];
		if (i + p[i] - 1 > R) 
			M = i, R = i + p[i] - 1;
	}

	int ans = 0;
	for (int i = 1; i <= m; i++) ans = max(ans, p[i]);
	//printf("%d\n",ans-1);
	return ans-1;
}

int idx;
int main(){
	while(scanf("%s",s+1)){
		if( *(s+1)=='E'&& *(s+2)=='N'&&*(s+3)=='D') break;
		int res=manacher();
		printf("Case %d: %d\n",++idx,res);
	}
	return 0;
}

我的视频题解空间

posted on 2021-05-18 13:29  itdef  阅读(123)  评论(0编辑  收藏  举报

导航