简介

Hash 的思想就是设计函数 f(A)f(A) 将某个数据类型映射为整数,由于相同的元素映射后得到的值仍然相同,所以当 f(x)=f(y)f(x)=f(y) 时认为 x=yx=y 即 Hash 的思想。

为了防止哈希碰撞即 f(x)=f(y)f(x)=f(y)xyx\ne y,可以多设计几个函数 f1,f2,f_1,f_2,\dots​ 尽可能避免哈希碰撞的发生。可以看出这是一个用概率来换复杂度的算法。

注意,任何数据结构都可以用哈希来判断是否相等,只不过字符串哈希用的比较多一点,所以本文归类为字符串。

[模板] 字符串哈希

给定长度为 nn 的字符串 ss,再给 mm 个询问,询问 sl1r1s_{l_1\sim r_1}sl2r2s_{l_2\sim r_2} 是否相同。其中 1n,m1051\le n,m\le 10^5

题目链接:AcWing 841

一种常见的字符串哈希的方法是将字符串看作一个 K=131K=131 进制的数字,然后对某个大质数 PP​​ 取模,因为其因子少可以减少碰撞概率。

以数组 hih_i 代表字符串 1i1\sim i 换做 KK 进制的数字 modP\bmod P 的结果,即 hi=j=1isj×Kijh_i=\sum_{j=1}^i s_j\times K^{i-j},有递推式 hi=hi1×K+sih_i=h_{i-1}\times K+s_ih0=0h_0=0

对于 iji\sim j 中的值,计算 hjhi×Kji+1h_j -h_i\times K^{j-i+1} 就可以得到结果了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <bits/stdc++.h>
using namespace std;

#define int long long
const int N = 100010, K = 131;
int n, m;
string s;

template<int P>
struct StringHash {
int h[N], pow[N];

int qmul(int a, int k) {
int res = 0;
while (k) {
if (k & 1) res = (res + a) % P;
a = (a + a) % P;
k >>= 1;
}
return res;
}

void init(const string& s) {
pow[0] = 1;
for (int i = 1; i <= s.size(); i++) {
pow[i] = qmul(pow[i-1], K);
h[i] = (qmul(h[i-1], K) + s[i-1]) % P;
}
}

int get(int l, int r) {
int res = (h[r] - qmul(h[l-1], pow[r-l+1])) % P;
if (res < 0) res += P;
return res;
}
};

StringHash<1'000'000'000'000'000'003ll> h1;
StringHash<1'000'000'000'000'000'009ll> h2;

signed main() {
cin >> n >> m >> s;
h1.init(s), h2.init(s);
for (int i = 1, l1, r1, l2, r2; i <= m; i++) {
cin >> l1 >> r1 >> l2 >> r2;
if (h1.get(l1, r1) == h1.get(l2, r2) && h2.get(l1, r1) == h2.get(l2, r2)) cout << "Yes\n";
else cout << "No\n";
}
return 0;
}