Hash 是一类将关键字集合映射到值集合的算法。在 OI 中的主要应用是将字符串转换为数值,提高查找的效率。
原理
这里将简单介绍用基数转换法实现的 Hash 函数。
我们将需处理的 $Key$ 值看做 $b$ 进制数,然后将它转换为十进制数,再对其取模。
例如 $Key=210867$ ,并将它看成是十三进制数 $(210867)_{13}$ ,然后将它转换为十进制数:
$$
(210867)_{13} = 2 imes 13 ^ 5 + 1 imes 13 ^ 4 + 8 imes 13 ^ 2 + 2 imes 13 + 7 = (772584)_{10}
$$
因为每个字符都有唯一的 ASCII 码,所以可以使用上面的方法将字符串转换为数值。
设字符串 $C=c_1c_2 cdots c_m$ 从左往右由高位到低位,定义 $H(C, k)$ 为字符串左起前 $k$ 位组成字符串的哈希值,$H(C) = H(C, m)$,左边为高位的定义是为了方便处理,以及压缩计算时间。
类似快速读入的递推方法,得到:
$$
H(C, k+1) = H(C, k) imes b+c_{k+1}
$$
即:
$$
H(C, k) = sum_{i=1}^k (c_i imes b^{k-i})
$$
当我们要计算子串 $C'=c_{k+1}c_{k+2}cdots c_{k+n}$ 时联想到十进制中:
$$
overline{de}=overline{abcde}-overline{abc} imes10^2
$$
所以:
$$
H(C') = H(C, k+n) - H(C, k) imes b^n
$$
最终我们只需预处理 $b^n$和递推求出 $H(C, k)$ 后,就可以在 $O(1)$ 时间内查询字符串任意子串哈希值。
实现
typedef unsigned long long ULL;
int bPow[lenMax+5];
ULL sum[lenMax+5];
void init(char str[], int b) {
bPow[0] = 1;
for (int i = 1; i <= lenMax; i++) {
bPow[i] = bPow[i - 1] * b;
}
int len = strlen(str);
sum[0] = 0;
for (int i = 0; i < len; i++) {
sum[i + 1] = sum[i] * b + (ULL)(str[i] - 'A' + 1);
}
}
ULL hash(int k, int n) {
return (ULL)sum[k + n - 1] - sum[k - 1] * bPow[n];
}