字符串哈希-Acwing-回文子串的最大长度
题目:
如果一个字符串正着读和倒着读是一样的,则称它是回文的。
给定一个长度为N的字符串S,求他的最长回文子串的长度是多少。
输入格式
输入将包含最多30个测试用例,每个测试用例占一行,以最多1000000个小写字符的形式给出。
输入以一个以字符串“END”(不包括引号)开头的行表示输入终止。
输出格式
对于输入中的每个测试用例,输出测试用例编号和最大回文子串的长度(参考样例格式)。
每个输出占一行。
输入样例:
abcbabcbabcba
abacacbaaaab
END
输出样例:
Case 1: 13
Case 2: 6
题意:
求 给 定 字 符 串 的 最 长 回 文 子 串 的 长 度 。 求给定字符串的最长回文子串的长度。 求给定字符串的最长回文子串的长度。
题解:
对 字 符 串 正 序 和 逆 序 分 别 哈 希 , 再 从 前 到 后 枚 举 回 文 子 串 的 中 心 , 二 分 回 文 半 径 , 输 出 最 大 的 回 文 半 径 。 对字符串正序和逆序分别哈希,再从前到后枚举回文子串的中心,二分回文半径,输出最大的回文半径。 对字符串正序和逆序分别哈希,再从前到后枚举回文子串的中心,二分回文半径,输出最大的回文半径。
细节方面:
- 由 于 最 大 长 度 可 能 时 奇 数 , 可 能 是 偶 数 , 为 了 优 化 代 码 复 杂 度 , 对 每 一 个 字 符 串 预 处 理 , 在 相 邻 两 个 字 符 之 间 插 入 一 个 相 同 的 其 他 字 符 , 将 长 度 为 S 的 字 符 串 转 化 为 S + S − 1 = 2 S − 1 的 长 度 为 奇 数 的 字 符 串 。 注 意 : 预 处 理 完 了 后 , 字 符 串 的 长 度 n 要 记 住 乘 2 , 防 止 遍 历 出 错 。 由于最大长度可能时奇数,可能是偶数,为了优化代码复杂度,对每一个字符串预处理,在相邻两个字符\\之间插入一个相同的其他字符,将长度为S的字符串转化为S+S-1=2S-1的长度为奇数的字符串。\\注意:预处理完了后,字符串的长度n要记住乘2,防止遍历出错。 由于最大长度可能时奇数,可能是偶数,为了优化代码复杂度,对每一个字符串预处理,在相邻两个字符之间插入一个相同的其他字符,将长度为S的字符串转化为S+S−1=2S−1的长度为奇数的字符串。注意:预处理完了后,字符串的长度n要记住乘2,防止遍历出错。
- 求 正 序 和 逆 序 的 哈 希 表 h l 和 h r 时 , 指 针 i , j 从 1 和 n 分 别 开 始 递 增 和 递 减 。 求正序和逆序的哈希表hl和hr时,指针i,j从1和n分别开始递增和递减。 求正序和逆序的哈希表hl和hr时,指针i,j从1和n分别开始递增和递减。
-
二
分
枚
举
中
点
在
i
的
回
文
子
串
的
半
径
时
,
答
案
必
然
小
于
等
于
字
符
串
长
度
的
一
半
,
因
此
二
分
的
左
右
端
点
l
=
0
,
r
=
m
i
n
(
i
−
1
,
n
−
i
)
。
此
外
,
在
求
取
中
点
右
半
部
分
也
就
是
回
文
子
串
的
后
半
部
分
时
,
区
间
端
点
要
翻
转
过
来
,
如
下
图
:
二分枚举中点在i的回文子串的半径时,答案必然小于等于字符串长度的一半,\\因此二分的左右端点l=0,r=min(i-1,n-i)。此外,在求取中点右半部分也就是\\回文子串的后半部分时,区间端点要翻转过来,如下图:
二分枚举中点在i的回文子串的半径时,答案必然小于等于字符串长度的一半,因此二分的左右端点l=0,r=min(i−1,n−i)。此外,在求取中点右半部分也就是回文子串的后半部分时,区间端点要翻转过来,如下图:
子 串 前 半 部 分 的 哈 希 表 区 间 为 [ i − m i d , i − 1 ] , 后 半 部 分 的 区 间 为 [ n − i + 1 − m i d , n − i + 1 − 1 ] 。 子串前半部分的哈希表区间为[i-mid,i-1],后半部分的区间为[n-i+1-mid,n-i+1-1]。 子串前半部分的哈希表区间为[i−mid,i−1],后半部分的区间为[n−i+1−mid,n−i+1−1]。
如 果 二 者 哈 希 值 不 等 , 说 明 半 径 二 分 得 过 大 , 则 r = m i d − 1 。 如果二者哈希值不等,说明半径二分得过大,则r=mid-1。 如果二者哈希值不等,说明半径二分得过大,则r=mid−1。
- 最 终 求 答 案 时 , 还 要 考 虑 回 文 串 两 端 的 情 况 , ① 、 若 两 端 是 字 母 , 说 明 字 母 比 添 加 的 符 号 多 一 个 , 那 么 回 文 串 长 度 为 ⌈ 2 l + 1 2 ⌉ 。 ② 、 若 两 端 是 新 增 的 符 号 , 说 明 符 号 比 字 母 多 一 个 , 那 么 回 文 串 长 度 就 是 回 文 半 径 。 ③ 、 维 护 所 有 回 文 半 径 中 的 最 大 值 即 可 得 最 终 答 案 。 最终求答案时,还要考虑回文串两端的情况,\\①、若两端是字母,说明字母比添加的符号多一个,那么回文串长度为\lceil\frac{2l+1}{2}\rceil。\\②、若两端是新增的符号,说明符号比字母多一个,那么回文串长度就是回文半径。\\③、维护所有回文半径中的最大值即可得最终答案。 最终求答案时,还要考虑回文串两端的情况,①、若两端是字母,说明字母比添加的符号多一个,那么回文串长度为⌈22l+1⌉。②、若两端是新增的符号,说明符号比字母多一个,那么回文串长度就是回文半径。③、维护所有回文半径中的最大值即可得最终答案。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ull unsigned long long
using namespace std;
const int N=1e6+10;
const int base=131;
char str[2*N];
ull hl[2*N],hr[2*N],p[2*N];
ull get(ull h[],int l,int r)
{
return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
int T=1;
while(~scanf("%s",str+1),strcmp("END",str+1))
{
///添加'{'符号
int n=strlen(str+1);
for(int i=2*n;i;i-=2)
{
str[i]=str[i/2];
str[i-1]='z'+1;
}
///长度增大
n*=2;
p[0]=1;
for(int i=1,j=n;i<=n;i++,j--)
{
hl[i]=hl[i-1]*base+str[i]-'a'+1;
hr[i]=hr[i-1]*base+str[j]-'a'+1;
p[i]=p[i-1]*base;
}
int res=0;
for(int i=1;i<=n;i++)
{
int l=0,r=min(i-1,n-i);
while(l<r)
{
int mid=l+r+1>>1;
///hl左端点i-mid,右端点i-1;hr左端点要翻转
if(get(hl,i-mid,i-1)!=get(hr,n-(i+mid)+1,n-(i+1)+1)) r=mid-1;
else l=mid; ///mid=l+r+1>>1
}
///字母多一个,回文半径是l,总长度为2l+1
if(str[i-l]<='z') res=max(res,(2*l+1)/2+1);///上取整
else res=max(res,l);
}
printf("Case %d: %d\n",T++,res);
}
return 0;
}