字符串哈希,求删除任一个字符后的字符串哈希值,双重哈希
题目
https://ac.nowcoder.com/acm/contest/1076/G
题目描述
给你两个只由小写字母组成的字符串S,T,保证S的长度大于等于T,现在你必须删除T串中一个字符,使得T串变成S串的子串,输出删除的位置,如果有多个位置合法,输出最小的那个,如果没有合法的位置,输出-1
输入描述:
第一行输入字符串
S
(
2
<
=
∣
S
∣
<
=
3
e
5
)
S(2 <= |S| <= 3e^5)
S(2<=∣S∣<=3e5)
第二行输入字符串
T
(
2
<
=
∣
T
∣
<
=
S
)
T(2 <= |T| <= S)
T(2<=∣T∣<=S)
输出描述:
输出一个数
示例1
输入
aaaab
aabaa
输出
3
示例2
输入
aaaa
aaa
输出
1
示例3
输入
abcd
xyz
输出
-1
思路:
简单哈希,把s串中连续长度为len_t-1的子串转化成相应数值,存到map里;再把t串中例举拿掉每个字符,把拿掉一个字符的子串转化为数值,若map中存在该值则找到合法答案了。
#include<iostream>
#include<map>
#include <string.h>
using namespace std;
const int N=3e5+10,P=131;
typedef unsigned long long ULL;
char s[N];
char t[N];
ULL hs[N];
ULL ht[N];
//int p[N];只要测试一组数据,快速幂就好,不用全部预处理,费时太多
ULL quickpow(ULL b,ULL e){
//参数的数据类型总要与返回值相同,int一定出错 ,大概快速幂的数据特点?
ULL res=1;
while(e){
if(e&1)res*=b;
b*=b;
e>>=1;
}
return res;
}
ULL getHs(int l,int r){
if(l>r)return 0;
return hs[r]-hs[l-1]*quickpow(P,r-l+1);
}
ULL getHt(int l,int r){
if(l>r)return 0;
return ht[r]-ht[l-1]*quickpow(P,r-l+1);
}
map<ULL,int> mp;
int main(){
cin>>s+1>>t+1;
int ls=strlen(s+1);
int lt=strlen(t+1);
// p[0]=1;
for(int i=1;i<=ls;i++){
// p[i]=p[i-1]*P;
hs[i]=hs[i-1]*P+s[i];
}
for(int i=1;i<=lt;i++){
ht[i]=ht[i-1]*P+t[i];
}
for(int i=1;i<=ls-lt+1;i++){
//只需要根据s的前缀哈希值求出长度为lt-1的子串的哈希就行ls-(lt-1)
mp[getHs(i,i+lt-2)]=1;//i+(lt-1)-1
}
// for(int i=lt-1;i<=ls;i++){
// ULL k=hs[i]-hs[i-lt+1]*quickpow(P,lt-1);
// mp[k]=1;
// }
//枚举t中删除t[i]的哈希值,ht[1->i-1]+ht[i+1,lt]
for(int i=1;i<=lt;i++){
ULL k=ht[i-1]*quickpow(P,lt-i)+getHt(i+1,lt);
//!!!两字符串拼接后的字符串的哈希值可不是直接相加,先给前面的字符串补0
if(mp[k]==1){
cout<<i;
return 0;
}
}
cout<<"-1";
return 0;
}
求删除任一个字符后的字符串哈希值
ULL k=ht[i-1]*quickpow(P,lt-i)+getHt(i+1,lt)
;
如何计算将原字符串改变若干个字母后的哈希值。(不过有一个限制,就是在求字符串的哈希时,只有一个变量,而不是开了一个数组记录每一位的哈希值)
公式推就不推了,只是记录下公式结果:
首先明确字符串下标从1开始,字符串长度是len,改变的元素是s[i],将s[i]要改为c
h = h + ( c − s [ i ] ) ∗ p ( l e n − i ) ; h=h+(c−s[i])∗p^{(len−i)}; h=h+(c−s[i])∗p(len−i);
双重哈希写法
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+10;
char s[maxn],t[maxn];
long long base[3]={0,23,29},mod[3]={0,100000000+7,100000000+9},h[3][maxn],f[3][maxn];
map<long long,int >mp_1,mp_2;
long long ksm(long long res,long long cnt,long long mo)
{
long long sum=1;
while(cnt>0)
{
if(cnt&1)sum=(sum*res)%mo;
res=(res*res)%mo;
cnt>>=1;
}
return sum;
}
int main()
{
//memset(h,0,sizeof(h));
//memset(f,0,sizeof(f));
scanf("%s%s",s+1,t+1);
int len_s=strlen(s+1);
int len_t=strlen(t+1);
for(int i=1;i<=len_s;i++)
{
for(int k=1;k<=2;k++)
h[k][i]=(h[k][i-1]*base[k]+s[i]-'a'+1)%mod[k];
}
long long res[3];
for(int i=len_t-1;i<=len_s;i++)
{
for(int k=1;k<=2;k++)
res[k]=(h[k][i]-h[k][i-len_t+1]*ksm(base[k],len_t-1,mod[k])%mod[k]+mod[k])%mod[k];
mp_1[res[1]]=1;
mp_2[res[2]]=1;
}
for(int i=1;i<=len_t;i++)
{
for(int k=1;k<=2;k++)
f[k][i]=(f[k][i-1]*base[k]+t[i]-'a'+1)%mod[k];
}
for(int i=1;i<=len_t;i++)
{
for(int k=1;k<=2;k++)
res[k]=(f[k][len_t]-f[k][i]*ksm(base[k],len_t-i,mod[k])%mod[k]
+f[k][i-1]*ksm(base[k],len_t-i,mod[k])%mod[k]+mod[k])%mod[k];
if(mp_1[res[1]]==1&&mp_2[res[2]]==1)
{
cout<<i;
return 0;
}
}
cout<<-1<<endl;
}
双重哈希模板
双重哈希属于开放地址哈希中的一种解决冲突方案,也就是说如果一次哈希不能解决问题的时候,要再次哈希,与再哈希方法不同的是,第二次使用的哈希函数与第一次是不同的:
https://www.cnblogs.com/energy1010/p/5802942.html
#include <iostream>
usinh namepace std;
typedef long long ll;
const int N=1e6+10;
const int mod1=1e9+7;//如有误差手动修改
const int mod2=1e9+9;
ll has1[N],has2[N];
ll bas1[N],bas2[N];//如果用quickpow就不用这个啦,主要看数据范围,决定预处理还是快速幂
const int p1=131,p2=13331;
// has1[0]=has2[0]=0;//哈希值不能为0,下标从1开始
bas1[0]=bas2[0]=1;
int len=strlen(s+1);
for(int i=1;i<=len;++i){//多串hash可以这块预处理
bas1[i]=(bas1[i-1]*p1)%mod1;
bas2[i]=(bas2[i-1]*p2)%mod2;
}
for(int i=1;i<=len;++i){
has1[i]=(has1[i-1]*p1+s[i])%mod1;
has2[i]=(has2[i-1]*p2+s[i])%mod2;
}
}
ll gethash1(int r,int len){//返回以r结尾长度len的子串has1值
return ((has1[r]-has1[r-len]*bas1[len])%mod1+mod1)%mod1;
}
ll gethash2(int r,int len){//返回以r结尾长度len的子串has2值
return ((has2[r]-has2[r-len]*bas2[len])%mod2+mod2)%mod2;
ll gethash11(int l,int r){//长度为 r-l+1
return ((has1[r]-has1[l-1]*bas1[r-l+1])%mod1+mod1)%mod1;
}
ll gethash22(int l,int r){//长度为 r-l+1
return ((has2[r]-has2[l-1]*bas2[r-l+1])%mod2+mod2)%mod2;
开两个map容器双管齐下,但是在同一个容器里要是对应的主串的哈希值和模式串的哈希值,即同一个哈希函数映射出来的哈希值,才能根据哈希值相等判断出字符串相等