字符串哈希

字符串哈希

字符串哈希就是将一个字符串映射为P进制的整数.

  1. 将一个字符串映射成一个P进制整数
    对于一个长度为n的字符串s,这样定义一个Hash函数:h(s)=i=1ns[i]×pni(modM)
    例如,字符串,abc,其哈希值为ap2+bp1+c
  2. 如果两个字符串不一样,哈希值却一样,这种现象称为哈希碰撞
  3. 解决哈希碰撞的方法:
    巧妙地设置P和M的值,保证P与M互质.
    P通常取质数131或者13331
    M通常取大整数264,把哈希函数值的数据类型定义为UUL(unsigned long long),超过则自动移除,等价于取模

解题步骤

  • 求一个字符串的哈希值相当于求前缀和,求一个字符串的子串哈希值相当于求区间和
  • 递推公式h[i]=h[i1]×P+s[i]
  • 求子串的哈希值也就是求区间和:h[l,r]=h[r]h[l1]×prl+1
  • 计算前缀和的时间复杂度为O(n), 查询子串哈希值的时间复杂度为O(1)

注意:本文图文并茂

将提供以下图文链接供大家理解:
图文链接:
飞书图解链接🎉🎉🎉
密码: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;
}

题目二

Acwing 3508. 最长公共子串

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许可协议进行许可。

posted @   爱情丶眨眼而去  阅读(75)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
🔑
  1. 1 赤伶 HITA
  2. 2 樱花树下的约定 (DJ-lucky小阳版) 旺仔小乔
  3. 3 踏雪 国风新语,Babystop_山竹
  4. 4 虞兮叹 闻人听書_
  5. 5 广寒宫 花沫
  6. 6 踏山河 七叔(叶泽浩)
  7. 7 破茧 张韶涵
  8. 8 下山 要不要买菜
  9. 9 红昭愿 音阙诗听
  10. 10 渡我不渡她 独孤
  11. 11 海草舞 陈冬霖
  12. 12 纸短情长 (女声版) 林玉冰
  13. 13 把梦照亮 赵小炮
  14. 14 沙漠骆驼 烟火兄弟
  15. 15 赢在江湖 姜鹏
  16. 16 孤勇者 杨宇峰
  17. 17 口是心非 张大帅
  18. 18 赐我 小时姑娘
  19. 19 囍(Chinese Wedding) 葛东琪
踏雪 - 国风新语,Babystop_山竹
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

踏雪 (国风新语原创企划《乐舞雅集》) - 国风新语/Babystop_山竹

词:烟十八

曲:董相麟@喜百音Hit Melody

编曲:浅逸

和声编唱:枕星烛

混音:啊鲤(龚骏混音工作室)

母带:龚骏

出品人:于明

统筹:唐潇/陈雅雪

发行:水星音乐

联合策划:国风新语

(未经著作权人许可,不得翻唱翻录或使用。)

冬风催 旧年岁 人未回

冬风催 旧年岁 人未回

望断千川只盼一个谁

几场喜悲 我颠沛

几场喜悲 我颠沛

一人空枕长夜难寐

一人空枕长夜难寐

红尘水 入酒杯 月下我饮醉

红尘水 入酒杯 月下我饮醉

大梦一场添几痕霜泪

风华已去 可曾悔

风华已去 可曾悔

儿女情长怎问错对

儿女情长怎问错对

我踏纷飞雪 听寒风冽冽

我踏纷飞雪 听寒风冽冽

转身挥剑 了断一腔眷恋

若往事如烟 飘荡山水间

若往事如烟 飘荡山水间

可否随风寄到你身边

我踏千秋雪 度似水流年

我踏千秋雪 度似水流年

无言冷月 听谁陈词唱遍

若情长半阙 爱该怎么写

若情长半阙 爱该怎么写

落笔你我不过伤离别

冬风催 旧年岁 人未回

冬风催 旧年岁 人未回

望断千川只盼一个谁

几场喜悲 我颠沛

几场喜悲 我颠沛

一人空枕长夜难寐

红尘水 入酒杯 月下我饮醉

红尘水 入酒杯 月下我饮醉

大梦一场添几痕霜泪

风华已去 可曾悔

风华已去 可曾悔

儿女情长怎问错对

儿女情长怎问错对

我踏纷飞雪 听寒风冽冽

我踏纷飞雪 听寒风冽冽

转身挥剑 了断一腔眷恋

若往事如烟 飘荡山水间

若往事如烟 飘荡山水间

可否随风寄到你身边

我踏千秋雪 度似水流年

我踏千秋雪 度似水流年

无言冷月 听谁陈词唱遍

无言冷月 听谁陈词唱遍

若情长半阙 爱该怎么写

若情长半阙 爱该怎么写

落笔你我不过伤离别

我踏纷飞雪 听寒风冽冽

我踏纷飞雪 听寒风冽冽

转身挥剑 了断一腔眷恋

转身挥剑 了断一腔眷恋

若往事如烟 飘荡山水间

若往事如烟 飘荡山水间

可否随风寄到你身边

我踏千秋雪 度似水流年

我踏千秋雪 度似水流年

无言冷月 听谁陈词唱遍

若情长半阙 爱该怎么写

落笔你我不过伤离别