字符串哈希
哈希:哈希可以理解为一种映射关系(或者可以理解为一种函数关系),在数字哈希里面是数字到数字的映射,在字符串哈希里是字符串到数字的映射,这种巧妙的映射关系保证了哈希算法在处理某些问题时具有高效性。
在字符串哈希为字符串到数字的映射关系。
我们假设一种映射关系为字符串到P进制的数的映射关系(P是任意的),那么就有了如何映射的问题:
在映射完成后要保证能完成一系列的字符串查找,比较,删除等操作,所以必须保证这种映射关系是一一映射的,不能有冲突(这也是字符串哈希和数字哈希的一大区别,对于数字哈希我们有专门的处理冲突的手段,因为应用范围的不一样,所以字符串哈希我们不会处理冲突),为了让冲突的最小化,所以我们这里取P = 131 or 13331
,并且将字符串映射到
2
64
2^{64}
264范围内的数字在这时的冲突几率非常小。
总结: p = 131 or 1331
, 在
2
64
2^{64}
264范围内的数字映射
字符串前缀哈希: 要想处理出来字符串任意区间内的hash, 我们首先要处理出来所有的前缀hash,例如:ABCDE
我们先处理出来h[1]
代表A
的hash,h[2]
代表B
的·······以此类推,处理办法:进制的求算公式:
例如十进制的数字:145:P = 10.
1
∗
1
0
2
+
4
∗
10
+
5
∗
1
0
0
1*10^2+4*10+5*10^0
1∗102+4∗10+5∗100 总结公式为下列:
a
×
P
4
+
b
×
P
3
+
c
×
P
2
+
d
×
P
1
+
e
×
P
0
a×P ^4+b×P ^3+c×P ^2+d×P ^1+e×P ^0
a×P4+b×P3+c×P2+d×P1+e×P0
要处理某一个前缀的hash值,要把字符串当成一个P进制的数,然后每一位的字母的ASCII值就代表这一位的数字,则abcde的hash值为上述式子
注解 :观察上式可得:存在指数爆炸的情况,所以有可能会超出我们要求的范围,想到了取模的操作,但是如果我们定义一个unsigned long long
的形式,他超出的部分会自动削减掉,就相当于了取模的操作,所以以这种操作来简化取模操作。
如果要处理某区间[l, r]
上的hash,那么就要用到进制运算的技巧
我们只需要知道h[l - 1] and h[r]
即可求得[l, r]
的hash值
其实我们要求的这个段上的hash值就是[l, r]这段上的P进制表示,所以要用到移位的运算
例如
把他处理成这种形式极为成功,处理的原理如下:
上面的数字是hash[r],下面的是hash[l - 1]
我们要求的就是中间标出来的那一段,显然我们是需要对hash[l - 1]进行移位,把他移到和hash[r]对头的位置,先找出来他俩的阶数之差为3,样例是在10进制下的情况,那么
h
a
s
h
[
l
−
1
]
×
1
0
3
=
123000
hash[l - 1] ×10^3 = 123000
hash[l−1]×103=123000,在和123456做差,得到的就是要求的区间上的hash值,即[l, r]这段上的P进制表示
公式如下:
H
a
s
h
[
L
,
R
]
=
(
H
a
s
h
[
R
]
−
H
a
s
h
[
L
−
1
]
×
P
R
−
L
+
1
)
Hash[L, R]=(Hash[R]−Hash[L−1]×P ^{R−L+1} )
Hash[L,R]=(Hash[R]−Hash[L−1]×PR−L+1)
acwing题干:字符串哈希
代码:
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const int N = 100010, P = 13331;
ULL h[N], p[N];
char str[N];
int n, m;
ULL get(int l, int r){
return h[r] - h[l - 1] * p[r - l + 1];
}
int main(){
cin >> n >> m >> str + 1;
p[0] = 1;
for(int i = 1; i <= n; i ++ ){
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;
if(get(l1, r1) == get(l2, r2)) puts("Yes");
else puts("No");
}
return 0;
}