字符串哈希

hash算法

对于整数hash来说,我们常用的是取余操作,碰到字符串,是无法直接进行取余的,这需要对字符串的hash算法进行设计。首先是要把字符串转化为一个整数,然后再将其取余,获得最终的hash值。

对于一个字符串AABCD,我们把它看作是一个P进制的数字(AABCD)p,将其转化为10进制,公式为:sum = A*P^4 + A*P^3 + B*P^2 + C*P^1 + D*P^0 ,其实就是N进制转10进制的公式。

之后我们对转化完的10进制数进行取余操作,即 sum mod Q

经验值

以上hash算法存在两个变量,P和Q的选取对hash冲突的概率至关重要,这里有一个经验值(前人经过大量使用总结),当P=131或13331且Q=2^64时,hash冲突的概率几乎为0。也就是说当PQ取经验值时,我们默认是不会出现哈希冲突的,也就不关心解决冲突的操作。

实现

例题:https://www.acwing.com/problem/content/description/843/

判断一字符串中,两个子串是否相等,可以使用字符串hash。求出字符串hash前缀,即可在O(1)时间内求出任意子串的hash值。

#include<iostream>

using namespace std;

const int N = 1e5 + 10, P = 131;

// 使用unsigned long long表示2的64次方,当数值溢出,溢出后的值和取余之后的值相同。
typedef unsigned long long ULL;

ULL h[N], p[N];
char str[N];

// h[i] = str[i] * P^0 + str[i-1] * P^1 + str[i-2] * P^2 + ...
// 求得[l, r]区间和,类似于前缀和中的[1, r]减去[1, l-1];在这里需要对[1, l-1]每一项乘P后,再相减。
ULL get(int l, int r) {
    return h[r] - h[l - 1] * p[r - l + 1];
}

int main() {
    int n, m;
    scanf("%d%d%s", &n, &m, str + 1);
    
    p[0] = 1;
    
    // 求前i个字符组成的字符串的哈希值,同时对P的i次方进行预处理
    for (int i = 1; i < N; i ++ ) {
        h[i] = h[i - 1] * P + str[i];
        p[i] = p[i - 1] * P;
    }
    
    while (m -- ) {
        int l1, r1, l2, r2;
        scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
        
        if (get(l1, r1) == get(l2, r2)) puts("Yes");
        else puts("No");
    }
    
    
    return 0;
}
posted @ 2022-01-13 12:01  moon_orange  阅读(74)  评论(0编辑  收藏  举报