字符串哈希(进制哈希)

哈希的过程,其实可以看作对一个串的单向加密过程,并且需要保证所加的密不能高概率重复(就像不能让隔壁老王轻易地用它家的钥匙打开你家门一样qwq),通过这种方式来替代一些很费时间的操作。

比如,最常见的,当然就是通过哈希数组来判断几个串是否相同(洛谷p3370)。此处的操作呢,很简单,就是对于每个串,我们通过一个固定的转换方式,将相同的串使其的“密”一定相同,不同的串 尽量 不同。

此处有人指出:那难道不能先比对字符串长度,然后比对ASCLL码之和吗?事实上显然是不行的(比如ab和ba,并不是同一个串,但是如是做却会让其认为是qwq)。这种情况就叫做hash冲突,并且在如此的单向加密哈希中,hash冲突的情况在所难免(bzoj就有这种让你给出一组样例,使得一段哈希代码冲突的题,读者可以尝试尝试)。

而我们此处介绍的,即是最常见的一种哈希:进制哈希。进制哈希的核心便是给出一个固定进制k,将一个串的每一个元素看做一个进制位上的数字,所以这个串就可以看做一个k进制的数,那么这个数就是这个串的哈希值;则我们通过比对每个串的的哈希值,即可判断两个串是否相同

下面上代码(洛谷P3370):

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define AA 1000007
typedef unsigned long long ull;
int n;
char s[AA];
ull a[AA];
ull k=131,prime=233317;//k 与 mod应该为互质的两个数,prime 是一个大质数 
ull mod=212370440130137957ll;
ull Hash(char s[]){
    int l=strlen(s);
    ull  ans=0;
    for(int i=0;i<l;i++){
        ans=(ans*k+(ull)s[i])%mod+prime;
    }
    return ans;
}
int main() {
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",s);
        a[i]=Hash(s);
    }
    sort(a+1,a+1+n);
    int ans=0;
    for(int i=1;i<n;i++){
        if(a[i]==a[i+1]){
            ans++;
        }
    }
    printf("%d",n-ans);
    return 0;
}

哈希还可以判断两个串s1,s2,求s1在s2中出现了多少次

设H(c,k)为前k个字符构成的字符串的哈希值

递推式:H(c,k+1)= H(c,k) * b + s[k+1]

计算字符串c从位置k+1开始的长度为n的子串的哈希值,可得递推式:

H(c') = H(c, k + n) - H(c,k) * b^n

例题:POJ3461

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string> 
using namespace std;
unsigned long long pow[1000005];
unsigned long long sum [1000005];
string s1;
string s2;
int main(){
	int T,ans = 0;
	int b = 31;
	pow[0] = 1;
	for(int i = 1; i <= 1000000; i++){
		pow[i] = pow[i-1] * b;
	}
	cin >> T;
	while(T--){
	ans = 0;
	cin >> s1;
	getchar();
	cin >> s2;
	int l1 = s1.size();
	int l2 = s2.size();
	sum[l2] = 0;
	for(int i = l2 - 1; i >= 0; i--){
		sum[i] = sum[i+1] * b + (unsigned long long)(s2[i] - 'A' + 1);
	}
	unsigned long long s = 0;
	for(int j = l1 - 1; j >= 0; j--){
		s = s * b + (unsigned long long)(s1[j] - 'A' + 1);
	}
	for(int i = 0; i <= l2 - l1; i++){
		if(s == sum[i] - sum[i + l1] * pow[l1]) ++ans;
	} 
	cout << ans << endl;
	}
	return 0;
} 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值