字符串 hash
一 字符串哈希
1.1 性质
- 哈希值不同,字符串一定不同。
- 哈希值相同,字符串不一定相同。(但大概率相同,并且我们希望它相同)
1.2 模数
根据一些数论知识,模数取质数是好的。
一个例子是
模数需要保证乘起来不超过 i64 ,通常需要常备两个模数。我的模数是
1.3 底数
显然底数需要大于字符集,通常要大于 256 即 ascii 码范围。随机取就行。
1.4 双重哈希
数据可能存在一些生日攻击(生日攻击可能很难理解,但原理基于生日悖论,是个容易理解且有意思的问题),可能需要双重哈希。
为了方便好写,哈希的时候开 i64 。
下面是一个模板:
const int MAXN = 2E5+5; const int HA = 2; const int BB[HA] = {1234, 5678}; const int PP[HA] = {int(1E9) + 123, int(1E9) + 403}; i64 pw[HA][MAXN], ipw[HA][MAXN]; i64 hdw[HA][MAXN], hup[HA][MAXN]; i64 ksm(i64 a, i64 n, int P) { i64 res = 1; for (;n;n>>=1, a=a*a%P) if (n&1) res=res*a%P; return res; } void init_hash() { for (int h = 0; h < HA; h++) { pw[h][0] = 1; for (int i = 1; i < MAXN; i++) { pw[h][i] = pw[h][i - 1] * BB[h] % PP[h]; } ipw[h][MAXN - 1] = ksm(pw[h][MAXN - 1], PP[h] - 2, PP[h]); for (int i = MAXN - 1; i; --i) { ipw[h][i - 1] = ipw[h][i] * BB[h] % PP[h]; } assert(ipw[h][0] = 1); } } void make_hash(std::string s) { // s[0] == ' ' int n = s.size(); for (int h = 0; h < HA; h++) { for (int i = 1; i <= n; i++) { hdw[h][i] = (hdw[h][i - 1] * BB[h] % PP[h] + s[i]) % PP[h]; hup[h][i] = (hup[h][i - 1] + s[i] * pw[h][i - 1] % PP[h]) % PP[h]; } } } std::array<int, 2> get_hdw(int l, int r) { assert(l <= r); std::array<int, 2> res; for (int h = 0; h < HA; h++) { res[h] = (hdw[h][r] - hdw[h][l - 1] * pw[h][r - l + 1] % PP[h] + PP[h]) % PP[h]; } return res; } std::array<int, 2> get_hup(int l, int r) { assert(l <= r); std::array<int, 2> res; for (int h = 0; h < HA; h++) { res[h] = (hup[h][r] - hup[h][l - 1] + PP[h]) % PP[h] * ipw[h][l - 1] % PP[h]; } return res; }
二 字符串 hash 公式
对一个字符串
模数: 一般选
系数: 一般随机成
2.1 降幂 hash
降幂 hash 是更常用的 hash ,较为简单。
2.2 升幂 hash
升幂哈希需要处理一次
2.3 二维 hash
二维 hash 是在一维上的扩展:
将每一维(行) hash ,于是可以
例题:
三 hash 的常见用法
3.1 字符串匹配
平凡模式串匹配:给一个文本串
伪代码:
function solve(text, pattern) : cnt = 0 0 ha1 = get_hash(text) ha2 = get_hash(pattern) for i : 1 to n - m + 1 : if ha1.subhash(i, i + m - 1) == ha2 : cnt += 1 return : cnt
参考列题:http://oj.daimayuan.top/course/22/problem/908
3.2 判断回文
给定一个长度为
一个简单的想法是,正向 hash 一次,反向 hash 一次,如果
另一个想法是,下降幂哈希一次,上升幂哈希一次,若子串哈希值相等,则回文。
证明:
例题: https://atcoder.jp/contests/abc070/tasks/abc070_a
const int MAXN = 2E5+5; const int HA = 2; const int BB[HA] = {1234, 5678}; const int PP[HA] = {int(1E9) + 123, int(1E9) + 403}; i64 pw[HA][MAXN], ipw[HA][MAXN]; i64 hdw[HA][MAXN], hup[HA][MAXN]; i64 ksm(i64 a, i64 n, int P) { i64 res = 1; for (;n;n>>=1, a=a*a%P) if (n&1) res=res*a%P; return res; } void init_hash() { for (int h = 0; h < HA; h++) { pw[h][0] = 1; for (int i = 1; i < MAXN; i++) { pw[h][i] = pw[h][i - 1] * BB[h] % PP[h]; } ipw[h][MAXN - 1] = ksm(pw[h][MAXN - 1], PP[h] - 2, PP[h]); for (int i = MAXN - 1; i; --i) { ipw[h][i - 1] = ipw[h][i] * BB[h] % PP[h]; } assert(ipw[h][0] = 1); } } void make_hash(std::string s) { // s[0] == ' ' int n = s.size(); for (int h = 0; h < HA; h++) { for (int i = 1; i <= n; i++) { hdw[h][i] = (hdw[h][i - 1] * BB[h] % PP[h] + s[i]) % PP[h]; hup[h][i] = (hup[h][i - 1] + s[i] * pw[h][i - 1] % PP[h]) % PP[h]; } } } std::array<int, 2> get_hdw(int l, int r) { assert(l <= r); std::array<int, 2> res; for (int h = 0; h < HA; h++) { res[h] = (hdw[h][r] - hdw[h][l - 1] * pw[h][r - l + 1] % PP[h] + PP[h]) % PP[h]; } return res; } std::array<int, 2> get_hup(int l, int r) { assert(l <= r); std::array<int, 2> res; for (int h = 0; h < HA; h++) { res[h] = (hup[h][r] - hup[h][l - 1] + PP[h]) % PP[h] * ipw[h][l - 1] % PP[h]; } return res; } void solve() { init_hash(); std::string s; std::cin >> s; int n = s.size(); s = " " + s; make_hash(s); int ok = 1; for (int h = 0; h < HA; h++) { ok &= get_hdw(1, n) == get_hup(1, n); } std::cout << (ok ? "Yes" : "No") << "\n"; }
3.3 回文信息合并
有时候反向哈希不容易维护,比如:容易维护从根借点开始的链上的正向哈希,不好维护叶结点开始的链上的反向哈希。
一个处理方法是,正向分别以下降幂和上升幂 hash 一次。于是区间信息和链上信息都可合并。
设下降幂 hash 数组为
这意味着可以轻易在链上或区间上合并下降幂和上升幂的哈希,然后判断是否回文。
3.4 最长回文子串
实际上它的线性做法是
给一个长度为
求有关最长回文子串的问题,一般都会将
好处:
回文串分奇偶讨论,所以回文半径也要分奇偶讨论。做出上述处理后,回文直径严格为二分半径。
如
如
不难用下降幂和上升幂哈希,枚举中点,求最长的二分半径。
3.5 常数失配匹配
非常固定的模型:给一个长度为
非常常见的技巧: 枚举起点,二分哈希值查询 lcp 。
考虑
容易想到 hash 后,二分出
如果
于是枚举
四 字符串 hash 的 string 判断优化
4.1 用 string 的哈希值做 map 键值
直接使用
用哈希值替代 string ,map 访问一组
4.2 本质不同子串数量
求长度为
不难想到一个暴力,伪代码如下:
function (s) : mp={} for len in range(1, n) : for i in range(1, n - len + 1) : mp[s[i:i+len-1]] = 1 return : len(mp)
这个算法的问题在于访问 string 是
优化是:字典可以使用散列表。string 可以换成哈希值。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧