传送门:Palindrome
题意:
给定一个长度为n的字符串,求它的最长回文串
字符串Hash:
- 直接使用
unsigned long long
储存Hash值,计算时不处理算术溢出问题(产生溢出时相当于自动对264取模,这样可以避免低效的mod运算) - 一般取进制数P为131或13331
- 在字符串S后加上字符c(其代表数值为
v
a
l
u
e
[
c
]
value\left[ c\right]
value[c])后整体Hash值为:
H ( S + c ) = ( H ( S ) ∗ P + v a l u e [ c ] ) m o d M H\left(S+c\right) =\left( H\left(S\right) \ast P+value\left[ c\right] \right) modM H(S+c)=(H(S)∗P+value[c])modM - 设在字符串S后加上字符串T后Hash值变为
H
a
s
h
(
S
+
T
)
Hash(S+T)
Hash(S+T),那么有:
H ( T ) = ( H ( S + T ) − H ( S ) × P l e n g t h ( T ) ) m o d M H\left( T\right) =\left( H\left( S+T\right) -H\left( S\right) \times P^{length\left( T\right)}\right) modM H(T)=(H(S+T)−H(S)×Plength(T))modM相当于P进制下在S后面补0的方式,把S左移到与S+T的左端对齐,然后相减即可
二分:
- 本题分两种情况:最长偶回文子串或最长奇回文子串,要综合两种情况的max
- 枚举回文子串的中心位置1~len,二分找到每个中心位置对应的回文子串(奇或偶)的max,最后再取一次max,其中二分回文子串长度的复杂度为 log N \log N logN,整体复杂度为 N log N N\log N NlogN
- 感觉所有循环从1开始要比0简单一些,相应字符串操作为
scanf("%s", s+1); len = strlen(s + 1);
代码:
#include <bits/stdc++.h>
using namespace std;
#define f(i,a,b) for(int i=a;i<b;i++)
#define ff(i,a,b) for(int i=a;i<=b;i++)
#define ull unsigned long long
const int N=1000000+5,P=13331;
char s[N];
//别开int,所有数组都用ull
ull p[N],p1[N],p2[N];
//无符号整数,不会出现负数
ull hash1(int l,int r){
return (p1[r]-p1[l-1]*p[r-l+1]);
}
//因为倒着存,注意用大的减去小的
ull hash2(int l,int r){
return (p2[l]-p2[r+1]*p[r-l+1]);
}
int Case;
int main(){
p[0]=1;
f(i,1,N)p[i]=p[i-1]*P;
//字符串从1开始读入
while(scanf("%s",s+1) && s[1]!='E'){
Case++;
int len=strlen(s+1);
//对s正序求hash
ff(i,1,len)p1[i]=p1[i-1]*P+s[i];
//对s逆序求hash
p2[len+1]=0;//如果上一个字符串比较长,那不重新给p2赋值则会出错
for(int i=len;i>=1;i--){
p2[i]=p2[i+1]*P+s[i];
}
//res赋值成1或0均可
int res=1;
//枚举中点位置i(奇数长度为中点,偶数长度为前一半的右端点)
//左串或右串长度为0也可以,hash值自然就是0了
ff(i,1,len){
//l值设为0,如果设为1的话在牛客只能过掉75%的数据
int l=0,r=min(i-1,len-i);
while(l<r){
//因为hash相等的时候可以取到,所以就用这个二分模板
int m=(l+r+1)>>1;
if(hash1(i-m,i-1)==hash2(i+1,i+m))l=m;
else r=m-1;
}
res=max(res,l*2+1);
//注意区分
//寻找最长偶回文子串
l=0,r=min(i-1,len-i+1);
while(l<r){
int m=(l+r+1)>>1;
if(hash1(i-m,i-1)==hash2(i,i+m-1))l=m;
else r=m-1;
}
res=max(res,l*2);
}
printf("Case %d: %d\n",Case,res);
}
}
第二次写的代码(类似的):
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ull unsigned long long
using namespace std;
const int N = 1000006, P = 13331;
char s[N];
ull f1[N], f2[N], p[N];
ull H1(int i, int j) {return (f1[j] - f1[i - 1] * p[j - i + 1]);}
ull H2(int i, int j) {return (f2[i] - f2[j + 1] * p[j - i + 1]);}
int main() {
int id = 0;
p[0] = 1; for (int i = 1; i < N; i++) p[i] = p[i - 1] * P;
while (~scanf("%s", s + 1) && s[1] != 'E') {
++id;
int ans = 0, len = strlen(s + 1);
f2[len+1] = 0;
for (int i = 1; i <= len; i++) f1[i] = f1[i - 1] * P + s[i];//不用处理溢出
for (int i = len; i; i--) f2[i] = f2[i + 1] * P + s[i];
for (int i = 1; i <= len; i++) {//枚举回文子串的中心位置
int min_len = 1, max_len = min(i - 1, len - i);//寻找最长奇回文子串,为防止越界,当2*i>=len时取后者
while (min_len < max_len){
int mid = (min_len + max_len + 1) >> 1;
if (H1(i - mid, i - 1) == H2(i + 1, i + mid)) min_len = mid;
else max_len = mid - 1;
}
ans = max(min_len * 2 + 1, ans);
min_len = 1, max_len = min(i - 1, len - i + 1);
while (min_len < max_len) {
int mid = (min_len + max_len + 1) >> 1;
if (H1(i - mid, i - 1) == H2(i, i + mid - 1)) min_len = mid;
else max_len = mid - 1;
}
ans = max(min_len * 2, ans);
}
printf("Case %d: %d\n", id, ans);
}
return 0;
}
//abcbabcbabcba中f1[1]=97,f1[2]=1293205
武辰延的字符串:
提示:
这题需要注意一下二分姿势,不然可能过不了
代码:
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll; // ull自动溢出,相当于对2^64取模
const int N=1e5+10,bas=13331; // bas自定,只要能尽量避免哈希冲突
int n1,n2;
ll sum1[N],sum2[N],p[N]; // sum1,sum2存哈希的前缀和,p[i]=bas^i
char s1[N],s2[N];
ll has1(int l,int r)//O(1)得到[l,r]区间内sum1的哈希值
{
return sum1[r]-sum1[l-1]*p[r-l+1];
}
ll has2(int l,int r)
{
if(r>n2)return 0;
return sum2[r]-sum2[l-1]*p[r-l+1];
}
int main(){
ios::sync_with_stdio(false);
cin>>s1+1>>s2+1;
n1=strlen(s1+1);
n2=strlen(s2+1);
sum1[0]=sum2[0]=0;
p[0]=1;
for(int i=1;i<=max(n1,n2);i++)
{
if(i<=n1)sum1[i]=sum1[i-1]*bas+s1[i];
if(i<=n2)sum2[i]=sum2[i-1]*bas+s2[i];
p[i]=p[i-1]*bas; //p[i]=bas^i
}
ll ans=0;
for(int i=1;i<n2;i++) //第2个字符串减去相同前缀[1,i]
{
if(s1[i]!=s2[i])break;
if(s2[i+1]!=s1[1])continue; //剪枝,剩下的第2个字符串连第1个字符都匹配不上
int l=1,r=n1,tmp=0;
while(l<=r)
{
int mid=(l+r)/2; // 假设长度为mid
ll t1=has1(1,mid); // 第1个字符串前缀哈希值
ll t2=has2(i+1,i+mid); // 第2个字符串剩余前缀哈希值
if(t1==t2)tmp=mid,l=mid+1; // 匹配上了,长度继续变大
else r=mid-1;
}
ans+=tmp;
}
printf("%llu\n",ans);
return 0;
}