字符串哈希
字符串哈希
字符串哈希就是将一个字符串映射为P进制的整数.
- 将一个字符串映射成一个P进制整数
对于一个长度为n的字符串s,这样定义一个Hash函数:
例如,字符串,abc,其哈希值为 - 如果两个字符串不一样,哈希值却一样,这种现象称为哈希碰撞
- 解决哈希碰撞的方法:
巧妙地设置P和M的值,保证P与M互质.
P通常取质数131或者13331
M通常取大整数,把哈希函数值的数据类型定义为UUL(unsigned long long
),超过则自动移除,等价于取模
解题步骤
- 求一个字符串的哈希值相当于求前缀和,求一个字符串的子串哈希值相当于求区间和
- 递推公式
- 求子串的哈希值也就是求区间和:
- 计算前缀和的时间复杂度为, 查询子串哈希值的时间复杂度为
注意:本文图文并茂
将提供以下图文链接供大家理解:
图文链接:
飞书图解链接🎉🎉🎉
密码:6&482J63
练习
题目一
Luogu P3370 【模板】字符串哈希
解法一:😤
AC代码,展开查看
#include<bits/stdc++.h> using namespace std; int main(){ unordered_set<string> set; int n; cin >> n; while(n -- ){ string s; cin >> s; set.insert(s); } cout << set.size(); return 0; }
解法二:
AC代码,展开查看
#include<bits/stdc++.h> using namespace std; typedef unsigned long long ULL; const int N = 1505, P = 131; ULL h[N]; char s[N]; ULL calc(char* s, int len){ for(int i = 1; i <= len; i ++ ){ h[i] = h[i - 1] * P + s[i]; } return h[len]; } int main(){ unordered_set<ULL> set; int n; scanf("%d", &n); while(n -- ){ scanf("%s", s); set.insert(calc(s, strlen(s))); } printf("%d", set.size()); return 0; }
题目二
AC代码,展开查看
#include<bits/stdc++.h> using namespace std; typedef unsigned long long ULL; const int N = 2e4 + 10, P = 131; char s[N]; ULL p[N], h[N]; int n, m; // 第一,二串字符串的长度 // 求子串哈希值 ULL get(int l, int r){ return h[r] - h[l - 1] * p[r - l + 1]; } // check 函数,求是否是公共子串 bool check(int x){ unordered_set<ULL> hash; for(int i = 1; i <= n - x + 1; i ++ ){ hash.insert(get(i, i + x - 1)); } // 将上面的 n - x + 1 中的 n 换为 n + m 即可 for(int i = n + 1; i <= n + m - x + 1; i ++ ){ if(hash.count(get(i, i + x - 1))) return true; } // 否则返回false return false; } // 二分查找,查找最大公共子串 0 <= x <= min(n, m) int find(){ int l = -1, r = min(n, m) + 1; // 可行区在左边 while(l + 1 < r){ int mid = l + r >> 1; if(check(mid)) l = mid; // 如果是公共子串放大 else r = mid; } return l; } int main(){ scanf("%s", s + 1); n = strlen(s + 1); scanf("%s", s + n + 1); m = strlen(s + n + 1); // 将第一,二串字符串合并 // 求字符串哈希 p[0] = 1; for(int i = 1; i <= n + m; i ++ ){ p[i] = p[i - 1] * P; char c = s[i]; if(c >= '0' && c <= '9'){ if(i <= n) c = '#'; else c = '$'; } h[i] = h[i - 1] * P + c; } // 因为要求最大公共子串,最大化,是否型 printf("%d", find()); return 0; }
题目三
Acwing 3875. 最长公共子串
使用与上题一致的解法.
AC代码,展开查看
// 这道题不能两两求最长公共子串, 因为两两最长公共子串可能不同, // 也不能先求两个字符串的最长公共子串,将求得的最长公共子串与剩下的字符串去求最长公共子串 // ,因为两两最长公共子串可能不同 // 使用字符串哈希 + 二分 #include<bits/stdc++.h> using namespace std; typedef unsigned long long ULL; const int N = 2e3 + 10, P = 131; ULL p[N], h[N]; int n, len = N; vector<string> strs; ULL get(int l, int r){ return h[r] - h[l - 1] * p[r - l + 1]; } bool check(int x){ unordered_map<ULL, int> m; for(int i = 0; i < n; i ++ ){ // 预处理字符串的哈希值 for(int j = 1; j <= strs[i].size(); j ++ ){ h[j] = h[j - 1] * P + strs[i][j - 1]; } unordered_set<ULL> s; for(int j = 1; j + x - 1 <= strs[i].size(); j ++ ){ s.insert(get(j, j + x - 1)); } for(auto item : s){ m[item] ++ ; } } for(auto &[k, v] : m){ if(v == n) return true; } return false; } int main(){ cin >> n; for(int i = 0; i < n; i ++ ){ string s; cin >> s; len = min(len, (int)s.size()); strs.push_back(s); } // 预处理p数组 p[0] = 1; for(int i = 1; i <= N; i ++ ) p[i] = p[i - 1] * P; // 二分, 0 <= 最长公共子串的长度 <= len, 所以 0 <= x <= len; int l = -1, r = len + 1; while(l + 1 < r){ int mid = l + r >> 1; if(check(mid)) l = mid; // 放大x else r = mid; // 可行区在左边 } printf("%d", l); return 0; }
本文参考自【董晓算法的个人空间-哔哩哔哩】
海纳百川,有容乃大!如果文章有什么不足之处,还请大神们评论区留言指出,我在此表达感谢🥰!若大家喜欢我的作品,欢迎点赞、收藏、打赏🎉🎉🎉!
本文作者:爱情丶眨眼而去
本文链接:https://www.cnblogs.com/zshsboke/p/17855310.html
版权声明:本作品采用©️CC BY-NC-SA 4.0许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步