哈希 hash
字符串哈希,将字符串映射为一个整数,利用这个整数快速地判断两个字符串是否相等。
令函数
主要性质
假设现在有两个字符串
- 当
时, 一定与 不同。 - 当
时, 不一定与 相同。
当
大致思想
首先设定进制数
然后将字符串看作一个
将其转为十进制,记得这个十进制数要模
const int P = 131, mod = 1e9 + 7; // p 为进制数,mod 为模数 const int N = 1e5 + 10; string s; int n, hsh[N], p[N]; int id (char c) { // 单个字符映射规则 return c - 'a'; } int get_Hash (int l, int r) { // 返回子串 [l, r] 的哈希值 int sum = 0; for (int i = l; i <= r; i++) { sum = (1ll * sum * P + id(s[i])) % mod; // 转十进制 } return sum; }
哈希冲突
当有
当
- 当
时,概率大致为 。 - 当
时,概率大致为 。 - 当
时,概率大致为 。 - 当
时,概率大致为 。 - 当
时,概率大致为 。 - 当
时,概率大致为 。 - 当
时,概率大致为 。 - 当
时,概率大致为 。 - 当
,概率已经小于了 !
当
当
- 当
时,概率大致为 。 - 当
时,概率大致为 。 - 当
时,概率大致为 ,趋近于 ! - 当
时(以下为研究所需,正常题目不可能出这么大的数据范围),概率大致为 ,仍然趋近于 ! - 当
时,概率大致为 ,也是几乎不会出现哈希冲突。 - 当
时,概率大致为 。
也就是说,当模数设为
注意,当模数设为 long long
计算可能会导致溢出,需要用 __int128
来计算,直接强制类型转换((__int128)1
)即可。
using ll = long long; // p 为进制数,mod 为模数 const int P = 131; const ll mod = 1e18 + 3; const int N = 1e5 + 10; string s; int n; ll hsh[N], p[N]; int id (char c) { // 单个字符映射规则 return c - 'a'; } ll get_Hash (int l, int r) { // 返回子串 [l, r] 的哈希值 ll sum = 0; for (int i = l; i <= r; i++) { sum = ((__int128)1 * sum * P + id(s[i])) % mod; // 转十进制 } return sum; }
附:测试代码如下:
#include <bits/stdc++.h> using namespace std; const int mod = 1e9 + 7; // const long long mod = 1e18 + 3; long double x = 1; int n; int main () { ios::sync_with_stdio(0), cin.tie(0); cin >> n; for (int i = 1; i <= n; i++) { x = x * (mod + 1 - i) / mod; } cout << fixed << setprecision(32) << x; return 0; }
快速求子串哈希值
可我们发现,上面的代码中求子串的哈希值仍然是
再观察一下,当字符串不会改变时,可以考虑预处理。
using ll = long long; // p 为进制数,mod 为模数 const int P = 131; const ll mod = 1e18 + 3; const int N = 1e5 + 10; string s; int n; ll hsh[N]; // 存储前缀哈希值 int id (char c) { // 单个字符映射规则 return c - 'a'; } int main () { ios::sync_with_stdio(0), cin.tie(0); cin >> s, n = s.size(), s = " " + s, p[0] = 1; for (int i = 1; i <= n; i++) { // 预处理前缀哈希值 hsh[i] = ((__int128)1 * hsh[i - 1] * P + id(s[i])) % mod; // 转为十进制数,hsh[i] 表示前 i 位哈希后的结果 } return 0; }
若要求子串
可以发现
for (int i = 1; i <= n; i++) { // 预处理前缀哈希值 p[i] = (__int128)1 * p[i - 1] * P % mod; // p[i] 表示 P 的 i 次方 % mod 的值 hsh[i] = ((__int128)1 * hsh[i - 1] * P + id(s[i])) % mod; // 转为十进制数,hsh[i] 表示前 i 位哈希后的结果 }
记得减法取模的细节。
ll Hash (int l, int r) { // 返回子串 [l, r] 哈希值 return (hsh[r] + mod - (__int128)1 * hsh[l - 1] * p[r - l + 1] % mod) % mod; }
附:生日悖论
生日悖论,指的是随机选出
由于这点反人类直觉,所以被称为“生日悖论”。
计算式子很类似哈希冲突,所以提到哈希往往都会提到生日悖论。
验证代码:
#include <bits/stdc++.h> using namespace std; long double x = 1; int n; int main () { ios::sync_with_stdio(0), cin.tie(0); cin >> n; for (int i = 1; i <= n; i++) { x = x * (366 - i) / 365; } cout << fixed << setprecision(32) << x; // 随机选择 n 个人,两两生日不同的概率 return 0; }
详见 OI Wiki。
可以用 map。
本文作者:wnsyou の blog
本文链接:https://www.cnblogs.com/wnsyou-blog/p/hash.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步