字符串哈希

字符串哈希

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

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

解题步骤

  • 求一个字符串的哈希值相当于求前缀和,求一个字符串的子串哈希值相当于求区间和
  • 递推公式\(h[i] = h[i - 1] \times P + s[i]\)
  • 求子串的哈希值也就是求区间和:\(h[l,r] = h[r] - h[l - 1] \times p^{r - l + 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;
}

本文参考自【董晓算法的个人空间-哔哩哔哩】

海纳百川,有容乃大!如果文章有什么不足之处,还请大神们评论区留言指出,我在此表达感谢🥰!若大家喜欢我的作品,欢迎点赞、收藏、打赏🎉🎉🎉!

posted @ 2023-11-25 11:03  爱情丶眨眼而去  阅读(26)  评论(0编辑  收藏  举报