字符串哈希表
将字符串转换为一个P进制的纯数字
每个字符,代表P进制上的一位。
abc和ab中的a,代表的值就不一样了
一个a是a的asc码*p的一次方,一个是a是a的asc码的*p的二次方
如图,abcde字符串对应的其哈希值
如上图所示
五个数的和,就是字符串的哈希值,这哈希值是唯一的
只有字符相同,长度相同,字符所在位置完全相同,字符串的哈希值才完全相同
为了避免哈希值重复,我们一般选择p=131或者p=13331
p的值
一般选择p=131 或 p=13331
- 质数的特性:131131 和 1333113331 是质数。质数作为基数能更均匀地分散字符串的哈希值,避免因字符分布规律导致的碰撞。
- 经验验证:这两个值在实践中被广泛测试,证明它们在绝大多数场景下能有效降低冲突概率。
- 自然溢出优化:当使用
unsigned long long
存储哈希值时,P×字符asc码 的乘法会自然溢出(等价于模 2的六十四次方),无需显式取模。
哈希值前缀和数组推导公式
h[N]设置为存储哈希值的数组,例如h[i],就是字符串第1个到第i个的哈希值
str[i]代表字符串中第i个字符的asc码
则h[i]=
h[i] = h[i-1]*P+str[i];
i-1是字符串的前一位
例如ab字符串添加c
ab*p,p进制哈希值,代表ab都进一位,得结果为ab0,添加c,得abc字符串哈希值
利用字符串哈希表查询
字符串哈希的题,往往会让我们查找L1-R1和L2-R2是否为相同字符串
拆开来看,就是查找字符串,指定区间内的哈希值
其实我们直接用前缀和的思想思考即可
例如查找l-r区间内字符串的哈希值
h[r]是前r个字符串的哈希值
h[l-1]是L前的字符串的哈希值
举例
字符串abcde,求3-5区间的哈希值,r为5,l为3
h[5]为h[r],也就是h[r]的哈希值是abcde
然后h[l-1],也就是h[2]的哈希值是ab
我们想求的l-r区间,实际上就是想求cde的哈希值
如果得到cde的哈希值,需要减去ab
但是h[2]的哈希值的ab,是两位数ab
abcde里的ab,实际上是ab000,所以想要得到cde的哈希值,需要ab000-abcde
我们知道,字符串的哈希值是p进制,那让ab变成ab000,只需要ab*p*p*p
也就是ab*p的(r-l+1)次方
设X为L-R字符串区间哈希值,得公式
ULL x=h[r]-h[l-1]*p[r-l+1];
经典例题和完整代码
AcWing - 算法基础课
#include<iostream>
using namespace std;
#define ULL unsigned long long
// typedef unsigned long long ULL;
const int N = 100003;
const int P = 131;
int p[N],h[N];//p[i]存储的是p的i次方,例如p[2]存储p的2次方
//返回区间字符串的哈希值
ULL que(int l,int r){return h[r]-h[l-1]*p[r-l+1];
}
int main(){int n,m;cin>>n>>m;char str[N];cin>>str+1;p[0]=1;h[0]=0;for(int i=1;i<=n;i++){//同时初始化p[N],存储p的次方p[i] = p[i-1]*P;//前缀和求整个字符串哈希值 h[i] = h[i-1]*P+str[i];}while(m--){int l1,r1,l2,r2;cin>>l1>>r1>>l2>>r2;cout<<(que(l1,r1)==que(l2,r2)?"Yes":"No")<<endl;}return 0;
}