1、何为字符串哈希?
字符串哈希又称为字符串前缀哈希法,将字符串利用函数f实现不同的字符串映射到不同的整数(哈希值)。
函数 f 可以方便地帮我们判断两个字符串是否相等。
字符串哈希的性质:
1、在 Hash 函数值不一样的时候,两个字符串一定不一样;
2、在 Hash 函数值一样的时候,两个字符串在理想状态下是一样的
(但有极小概率不一样,将 Hash 函数值一样但原字符串不一样的现象称为哈希碰撞)
2、字符串哈希如何映射?
X 1 X 2 X 3 X 4 … X n − 1 X n X_1 X_2 X_3X_4 \ldots X_{n-1}X_n X1X2X3X4…Xn−1Xn
如以上字符串所示,我们可以采用字符的ASCII 码乘上P的次方在模上一个大整数Q来计算哈希值,公式如下:
将字符串转化为整数:
h
(
X
1
…
X
n
)
=
(
X
1
×
P
n
−
1
+
X
2
×
P
n
−
2
+
…
+
X
n
−
1
×
P
1
+
X
n
×
P
0
)
h(X_1\ldots X_n)=(X_1 \times P^{n-1} + X_2 \times P^{n-2} + \ldots + X_{n-1} \times P^1 + X_n \times P^0)
h(X1…Xn)=(X1×Pn−1+X2×Pn−2+…+Xn−1×P1+Xn×P0)
对字符串对应的整数值取模,进行哈希映射:
h
a
s
h
(
X
1
…
X
n
)
=
h
(
X
1
…
X
n
)
m
o
d
Q
hash(X_1\ldots X_n)= h(X_1\ldots X_n)\mod Q
hash(X1…Xn)=h(X1…Xn)modQ
注意:
1、不能将字符映射为 0 ,在此前提下,字符映射可以随意,此处为了方便直接采用字符的ASCII 码
。
2、根据经验,一般将设置P 为131 或 13331
, Q为2^64
时,此时我们可以认为不产生哈希碰撞的冲突。
3、取模时,用 unsigned long long
类型来存储变量。
因为unsigned long long
它能够存储的最大值是
2
64
−
1
2^{64} - 1
264−1
它是一个 64 位的无符号整数,所以能够表示的数值范围是从
0
——
2
64
−
1
0 ——2^{64} - 1
0——264−1
如果h
数组存储的数据超过了unsigned long long
类型的最大值,将会发生数据变量溢出了,它会回绕到 0进行存储,因此这个过程相当于对2^64取模
了,就不需要用h mod Q
了。
3、如何应用?
应用的字符串前缀哈希的核心在于以下两个公式:
1、利用前缀和的思想,求字符串s的前缀哈希
h
[
i
]
=
h
[
i
−
1
]
×
p
+
s
[
i
]
,
h
[
0
]
=
0
(
1
)
h[i] = h[i-1] \times p + s[i],h[0]=0 \quad\quad(1)
h[i]=h[i−1]×p+s[i],h[0]=0(1)
如图中例子所示,利用公式(1),给定字符串s,就可以通过公式求出字符串的前缀哈希,并将其保存到h数组当中
2、利用字符串s的前缀哈希,求区间和,求任意每一段字符串的哈希(即每段区间的哈希值)
h
[
l
.
.
.
r
]
=
h
[
r
]
−
h
[
l
−
1
]
×
p
(
r
−
l
+
1
)
(
2
)
h[l...r] = h[r] -h[l-1] \times p^{(r-l+1)} \quad\quad(2)
h[l...r]=h[r]−h[l−1]×p(r−l+1)(2)
如下图所示,利用公式2,我们可以求出给定字符串s的每段区间和,从而通过比较每段字符串的哈希是否相同来判断,两段字符串是否相等。
4、字符串哈希算法例题
利用上面的描述,用代码来解决 AcWing 841. 字符串哈希
AC代码如下:
#include<iostream>
using namespace std;
const int N = 100010;
typedef unsigned long long ULL;
char s[N];
//P 取131 或 13331
int P = 131;
//h数组记录哈希,p记录对应的次方
ULL h[N], p[N];
//获取某个区间字符串的哈希
ULL get(int l, int r) {
return h[r] - h[l-1] * p[r - l + 1];
}
//构造字符串前缀哈希
void init_str_hash(char s[],int n) {
p[0] = 1;
for (int i = 1; i <= n; i++) {
h[i] = h[i - 1] * P + s[i];
//存储的是131的i次方
p[i] = p[i - 1] * P;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> s[i];
init_str_hash(s, n);
while (m--) {
int l1, r1,l2,r2;
cin >> l1 >> r1 >> l2 >> r2;
if (get(l1, r1) == get(l2, r2)) cout << "Yes" << '\n';
else
cout << "No" << '\n';
}
return 0;
}