题目链接:https://www.acwing.com/problem/content/description/141/
题目
如果一个字符串正着读和倒着读是一样的,则称它是回文的。
给定一个长度为 N N N 的字符串 S S S,求他的最长回文子串的长度是多少。
输入格式
输入将包含最多
30
30
30 个测试用例,每个测试用例占一行,以最多
1000000
1000000
1000000 个小写字符的形式给出。
输入以一个以字符串 END
开头的行表示输入终止。
输出格式
对于输入中的每个测试用例,输出测试用例编号和最大回文子串的长度(参考样例格式)。
每个输出占一行。
输入样例:
abcbabcbabcba
abacacbaaaab
END
输出样例:
Case 1: 13
Case 2: 6
思路:字符串哈希+二分 ( N l o g N NlogN NlogN)
为什么可以二分?
因为求最大半径最有单调性,即大的半径是回文串则小的半径一定是回文串
所以这里是前半段满足条件,后半段不满足,用二分模板
2
2
2
字符串预处理:
h
l
[
]
hl[]
hl[]表示字符串正序的哈希值、
h
r
[
]
hr[]
hr[]表示字符串后序的哈希值
将回文串分为两大类:
- 长度为奇数
- 长度为偶数
奇数的情况:
枚举每个字母作为中心点,二分求半径(左右两边的长度),判断哈希值是否一样,一样就扩大,不一样就缩小
偶数的情况:
先在原始字符串每两个字母中间添加一个没有出现过的字符,就可以将这种情况转化为奇数的情况(中心点可以是加入的字符)
为什么会变成奇数的情况:
假设原来字符串的长度为S
,在每两个字符中间补上的字符数量为S-1
,所以字符串长度为2S-1
找到半径,怎么求不包含添加字符的长度?
看当前字符串左右两边是不是字母
是,说明字母多一个,字符串长度应为
2
∗
S
+
1
2
\frac{2*S+1}{2}
22∗S+1 上取整,即
S
+
1
S+1
S+1
不是,说明添加的字符多一个 字符串长度应为
2
∗
S
+
1
2
\frac{2*S+1}{2}
22∗S+1 下取整,即
S
S
S
get的下标怎么求?
左边(顺序)的直接求
g
e
t
(
i
−
m
i
d
,
i
−
1
)
get(i - mid, i - 1)
get(i−mid,i−1)
右边(逆序)的应该是
g
e
t
(
i
+
1
,
i
+
m
i
d
)
get(i + 1, i + mid)
get(i+1,i+mid)
但是右边的应该对应到逆序数组里的下标
逆序数组里
1
1
1 对应的是
n
n
n,
2
2
2 对应的是
n
−
1
n-1
n−1,
3
3
3 对应的是
n
−
2
n-2
n−2
则
x
x
x 对应的就是
n
−
x
+
1
n-x+1
n−x+1
所以应该是
g
e
t
(
n
−
(
i
+
1
)
+
1
,
n
−
(
i
+
m
i
d
)
+
1
)
get(n - (i + 1) + 1, n - (i + mid) + 1)
get(n−(i+1)+1,n−(i+mid)+1)
因为逆序,应该要交换顺序,小的在前大的在后
所以右边的最终应该是
g
e
t
(
n
−
(
i
+
m
i
d
)
+
1
,
n
−
(
i
+
1
)
+
1
)
get(n - (i + mid) + 1, n - (i + 1) + 1)
get(n−(i+mid)+1,n−(i+1)+1)
AC代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef unsigned long long ULL;
const int N = 2000010,P = 131;
char str[N];
ULL hl[N],hr[N],p[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(str+1,"END")) // 逗号表达式,返回最后一个表达式的值
{
int n = strlen(str+1);
for(int i = n * 2; 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] * P + str[i] - 'a' + 1;
hr[i] = hr[i-1] * P + str[j] - 'a' + 1;
p[i] = p[i-1] * P;
}
int ans = 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;
if(get(hl,i - mid,i - 1) == get(hr,n - (i + mid) + 1,n - (i + 1) + 1)) l = mid; // 注意逆序的下标
else r = mid - 1;
}
if(str[i - l] <= 'z') ans = max(ans,l + 1); // 字母多一个,上取整
else ans = max(ans,l); // 字符多一个,下取整
}
printf("Case %d: %d\n",T++,ans);
}
return 0;
}