哈希表
💡 核心思想
哈希表通过哈希函数将键映射到数组下标,实现 $O(1)$ 平均时间的插入、删除和查询。竞赛中常用 unordered_map 或手写哈希(开放寻址法/拉链法)。字符串哈希用于 $O(1)$ 比较字符串是否相等。
竞赛中哈希最常用的两个场景:一是用 unordered_map 代替 map(更快但需要注意哈希冲突被卡),二是字符串哈希(将字符串映射为一个整数,$O(1)$ 比较子串是否相等)。字符串哈希的核心是取模防溢出 + 双哈希防冲突。
🎯 直觉理解
哈希表就像一个"超大的抽屉柜",每个抽屉有一个编号。你通过一个函数把物品的名称转换成抽屉编号,然后放进去。找东西时也用同样的函数算出抽屉编号,直接打开找。如果两个人被分到同一个抽屉(哈希冲突),就在抽屉里逐个比对。
📝 算法流程
- 选择哈希函数(字符串常用多项式哈希)
- 处理冲突:拉链法或开放寻址法
- 字符串哈希:预处理前缀哈希值,$O(1)$ 比较子串
- 双哈希:用两个不同模数降低冲突概率
$$H(s) = \sum_{i=0}^{n-1} s[i] \cdot b^{n-1-i} \pmod{M}$$
$$\text{子串哈希:} H(s[l..r]) = H(s[1..r]) - H(s[1..l-1]) \cdot b^{r-l+1} \pmod{M}$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | $O(1)$ 平均 |
| 空间 | $O(n)$ |
💻 参考实现(C++)
C++ (C++17)
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const ull BASE = 131;
ull h[1000005], pw[1000005];
ull getHash(int l, int r) { return h[r] - h[l-1] * pw[r-l+1]; }
int main() {
string s; cin >> s; int n = s.size();
pw[0] = 1; h[0] = 0;
for (int i = 1; i <= n; i++) {
h[i] = h[i-1] * BASE + s[i-1];
pw[i] = pw[i-1] * BASE;
}
int q; cin >> q;
while (q--) {
int l1,r1,l2,r2; cin >> l1 >> r1 >> l2 >> r2;
cout << (getHash(l1,r1)==getHash(l2,r2)?"Yes":"No") << "\n";
}
return 0;
}⚠️ 常见坑点
unsigned long long 自然溢出等价于取模 $2^{64}$
字符串哈希的 base 和 mod 要选择合适
单哈希可能被卡,重要比赛用双哈希
子串哈希的公式推导要仔细,容易差一个位置
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P3370 字符串哈希 | 洛谷 | CSP-S | 哈希判重 |
| P3808 AC自动机 | 洛谷 | CSP-S | Trie+哈希思想的扩展 |