字符串哈希,求删除任一个字符后的字符串哈希值,双重哈希

题目

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) S2<=S<=3e5

第二行输入字符串 T ( 2 < = ∣ T ∣ < = S ) T(2 <= |T| <= S) T2<=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+(cs[i])p(leni);

双重哈希写法

#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容器双管齐下,但是在同一个容器里要是对应的主串的哈希值和模式串的哈希值,即同一个哈希函数映射出来的哈希值,才能根据哈希值相等判断出字符串相等

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值