算法 | 字符串哈希
字符串哈希:定义一种将字符串映射到一个整型哈希值的函数
字符串哈希函数
引例
将
将
根据上面的例子,我们可以将字符串看为一个
定义
对于一个长度为
这种多项式形式的哈希函数,字符串最左侧为多项式高位,最右侧为低位,相反的实现也是同样可行的,本文不讲另一种。
该哈希函数有两个参数,分别是底数
下面给出一些常用的选择,不过也要小心出题人把常用值给卡掉:
其中 unsigned long long int
数据类型即可,范围
另外,因为字符串哈希处理冲突的开销较大,因此我们是不处理冲突的。所以用哈希处理字符串的题目,能不能过还是很玄学的。如果实在被卡掉了,可以试一下选择两个模数进行双哈希,这样的话哈希函数的值域就能扩大到两者之积,可以降低错误率。
代码
typedef long long ll;
constexpr ll P = 449, MOD = 436522843;
ll get_hash(string &s)
{
ll res = 0;
for (int i = 0; i < s.size(); i++)
res = (res * P % MOD + s[i]) % MOD;
return res;
}
子串哈希
原理
单次计算一个字符串的哈希值复杂度是
字符串哈希具有类似前缀和的性质,如果预处理了该字符串的每个前缀子串的哈希值,那么就可以在
具体来说,将前缀哈希值储存在
代码实现时,为了加快效率,将
演示
我们用一个可视化的例子来演示:
我们已知的是:
- 第一行:
的哈希值(仅作对比参考,实际算法是用不上的) - 第二行:
子串的哈希值 . - 第三行:
子串的哈希值 .
我们要求的是:
- 第四行:
字串的哈希值。
通过比对我们可以发现:
代码
struct StrHash
{
int len, base, mod;
vector<int> p, h;
void init(const string &s, int base, int mod)
{
len = s.size();
this->base = base;
this->mod = mod;
p.resize(len + 1);
h.resize(len + 1);
p[0] = 1;
for (int i = 1; i <= len; i++)
p[i] = p[i - 1] * base % mod;
h[0] = s[0] % mod;
for (int i = 1; i < len; i++)
h[i] = (h[i - 1] * base % mod + s[i]) % mod;
}
int get(int l, int r)
{
if (l <= 0)
return h[r];
return (h[r] - h[l - 1] * p[r - l + 1] % mod + mod) % mod;
}
};
允许 次失配的字符串匹配
问题
给定长为
解法
该问题无法使用 KMP 来解决,但是可用哈希 + 二分完成。思路如下:
枚举所有可能匹配的子串,假设现在枚举的子串为
时间复杂度
代码
/* 依赖上文的StrHash结构体 */
bool check(StrHash &a, StrHash &b, int toler)
{
if (a.len < b.len) // make a >= b
return check(b, a, toler);
int la = a.len, lb = b.len;
for (int i = 0; i <= la - lb; i++)
{
int err = 0, pos = 0;
while (err <= toler && pos < lb)
{
int l = pos, r = lb - 1;
while (l < r)
{
int mid = (l + r) / 2;
if (a.get(i + pos, i + mid) == b.get(pos, mid))
l = mid + 1;
else
r = mid;
}
if (a.get(i + pos, i + l) != b.get(pos, l))
err++;
pos = l + 1;
}
if (err <= toler)
return true;
}
return false;
}
回文判定
回文判定的相关问题,也可以用字符串哈希来解决,核心思路就是预处理出原串的哈希和逆序串的哈希。
不过这种问题 manacher 算法往往也能完成,并且不会被卡。
比较典型的例题可以是:Codeforces Global Round 7 - D2
本文采用 CC BY-SA 4.0 许可,本文 Markdown 源码:Haotian-BiJi