哈希学习笔记

upd:好像写的很杂糅。。。懒得改了。。。


板:P3370 【模板】字符串哈希
复杂度:O(n)
用途:将一串字符串映射到一个数


怎么写?

选取一个较小质数 p 选取一个较大质数 mod1
将字符串转换成一个 p 进制在 mod1 定义下的数
常见模数:1e9+7(但很容易被卡)
不太容易被卡的模数:19260817 123456791

写法如下:

image


哈希冲突

根据哈希的写法 我们不难发现 相同的字符串的 hash 值一定相等
但 hash 值相等的字符串一定相同吗? 其实不然

事实上 是有可能存在两个不同字符串转换后的 hash 值相同的情况 我们将其称之为哈希冲突
这里给出结论 若字符串的长度为 \(n\) 则模数若不超过 \(n^2\) 就很容易发生哈希冲突

那么如何避免 hash 冲突呢?

1.unsigned long long

unsigned long long 的数据范围为 \(0-2^{64}-1\) 当取值大于 \(2^{64}-1\) 时会自动溢出 相当于是自动对 \(2^{64}\) 取模

缺点:有概率会被丧心病狂的出题人卡

2.双哈希

顾名思义 就是准备两个模数 做两遍哈希

优点:我们认为是无法被 hack 的

缺点:常数巨大 取模和除法运算又很慢 有可能慢到 TLE

3.哈希表

优点:安全

缺点:我不会写 写起来有点麻烦

4.unordered_map

有点:写起来非常方便

缺点:STL...懂得都懂


同时 哈希还支持查询子串的 hash 值
需要 O(n) 的复杂度 O(1) 查询

首先我们要开一个数组 把之前每一步的 ch 记录下来
用ch[i] 表示 p 的 i 次方

我们以十进制数为例 观察如何查询中间一段数字的值
如这段:123456789 我们要查询第 3 位至第 7 位 即34567

我们已知每一段前缀的 hash 值 不难发现我们要用到 hash[2] = 12 与 hash[7] = 1234567

然后我们发现 $34567 = 1234567 - 12 * 10^5 $

根据这个式子 我们可以推广出 \(hash[l,r] = ha[r] - ha[l-1] * ch[r-l+1]\)
具体代码如下:

image


二维哈希

才发现之前没写 补一个(
对于预处理 我们横着 hash 一遍再竖着 hash 一遍:

for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= m; ++j) ha[i][j] = ha[i][j - 1] * p1 + a[i][j];
}
for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= m; ++j) ha[i][j] += ha[i - 1][j] * p2;
}

对于查询

image

code:

inline int query(int x1, int y1, int x2, int y2) {
    return ha[x2][y2] - ha[x1 - 1][y2] * ch2[x2 - x1 + 1] - ha[x2][y1 - 1] * ch1[y2 - y1 + 1] + ha[x1 - 1][y1 - 1] * ch1[y2 - y1 + 1] * ch2[x2 - x1 + 1];
}

hash 与 kmp 的区别:kmp 主要对于原字串的不同前缀问题较为好用 而 hash 则更加适用于多次询问 每次查询原字串中间一段区间的问题

posted @ 2023-02-12 10:50  Steven24  阅读(86)  评论(4编辑  收藏  举报