哈希入门

本文深入介绍了字符串哈希的基本概念和操作,包括如何将字符串转化为整数、求子串哈希值以及合并子串哈希。通过实例展示了如何使用哈希快速比较字符串,以及在ACM竞赛中可能遇到的题目类型。文章强调了哈希取模大质数的重要性以减少冲突,并提供了相关算法的C++实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写在最前面的话

为了便于广大的算法爱好者和本人复习、加深对算法的理解,故持续更新各种基础算法,每个算法都由深入介绍算法和实战操练两部分构成,题目由易入难,由浅入深,适合备考和复习,所选例题来自洛谷、牛客、LOJ、ZOJ、BZOJ、HDU等。总体上以牛客ACM竞赛算法知识点进行归类整理,希望大家学习以后都能有所收获!

哈希入门介绍

众所周知,比较两个字符串是否相同复杂度在最差情况下是 O ( n ) O(n) O(n),虽然期望是 O ( 1 ) O(1) O(1),但很容易就会被毒瘤出题人卡成 O ( n ) O(n) O(n),于是字符串哈希就应运而生了。不过还是很容易被卡,我在最后结束时会结合BZOJ上的 H a s h K i l l e r HashKiller HashKiller分析卡哈希的办法为什么不直接双哈希

哈希的主要思想就是把一个字符串转换成一个大整数,这样比较两个字符串是否相等的就只要比较两个整数是否相等就行了,妥妥稳定的 O ( 1 ) O(1) O(1)比较。下面先来介绍如何把字符串转变成一个整数。

我们考虑一个字符串中的每一个字符都可以对应一个ACSll码,于是就可以把每一个字符乘上一个进制数再取模,就像十进制一样,一个字符串 s s s的第 i i i位字符(下标从1开始)所对应的哈希值就是 s [ i ] ∗ b a s e n − i s[i]*base^{n-i} s[i]baseni,这里 b a s e base base是我们自己选择的进制数,一般可以选233,23333,10007等,不要太小就行。这样对每一位字符进行哈希并且累加,我们就得到了一整串字符的哈希值。
如:当 n = 3 n=3 n=3时, h a s h ( s ) = s [ 1 ] ∗ M 2 + s [ 2 ] ∗ M + s [ 3 ] ; hash(s)=s[1]*M^2+s[2]*M+s[3]; hash(s)=s[1]M2+s[2]M+s[3]
n = 4 n=4 n=4时, h a s h ( s ) = s [ 1 ] ∗ M 3 + s [ 2 ] ∗ M 2 + s [ 3 ] ∗ M + s [ 4 ] ; hash(s)=s[1]*M^3+s[2]*M^2+s[3]*M+s[4]; hash(s)=s[1]M3+s[2]M2+s[3]M+s[4]
一般地 h a s h ( s ) = ∑ i = 1 n s [ i ] ∗ b a s e n − i hash(s)=\sum_{i=1}^{n}s[i]*base^{n-i} hash(s)=i=1ns[i]baseni
求一个字符串的哈希值代码如下:

int hash(char *s)
{
   
	int len = strlen(s), ans = 0;
	for (int i = 1; i <= len; i++)
		ans = ((ll)ans * M + s[i]) % N;
	return ans;
}

每次累加时模上一个大质数,大多采用1e9+7、1e9+9、998244353等,当然也可以选择自然溢出,就是不进行取模操作。选择大质数的原因是我们想要每一个字符串尽可能对应唯一的哈希值,选择的质数小了或者选择合数会使得发生冲突的概率大大上升,这边可以参考生日攻击,同时这也是我们hack哈希的原理。

字符串哈希的基本操作

比起得到整个字符串的哈希值,更多时候我们更希望得到该字符串某一子串的哈希值,即给定区间 [ l , r ] [l,r] [l,r],求这段区间内子串哈希值。或者求区间 [ l 1 , r 1 ] 、 [ l 2 , r 2 ] [l_1,r_1]、[l_2,r_2] [l1,r1][l

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值