字符串前缀哈希

138 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第32天,点击查看活动详情

字符串前缀哈希

第一步:先预处理出所有前缀的哈希值 , h[i]:表示前i个字符的哈希值

image-20221105111229739

如何定义某一个前缀的哈希值?

第一步:把字符串当成p进制的数,这个字符串有10个字母,就堪称有10位

第二步:把p进制的数转为10进制的数,因为这个数可能很大,所以要对这个结果进行取模Q,通过取模,把这个数映射到0~Q-1的位置

image-20221105111423305

前缀和公式:h[i] = h[i-1]*p +str[i]

第一步:把字符串看作P进制数      如“ABCD”可以表示为(1234)p
第二步:把P进制数转化为10进制数   (1234p)=(1×p^3+2×p^2+3×p^1+4×p^0)
第三步:模运算: 对这个10进制数取模

根本就是:把字符串变成一个p进制数字(哈希值),实现不同的字符串映射到不同的数字

image-20221105112246036

  • 注意:任意字符不可以映射成0,否则会出现不同的字符串都映射成0的情况,比如A,AA,AAA皆为0,为了方便,我们可以直接使用字母ascii映射
  • 冲突问题:通过巧妙设置P (131 或 13331) , Q = 2^64的值,一般可以理解为不产生冲突
  • 而对于%2^64,可以采用unsigned long long,这样的话,溢出的时候,就是自动取模了

已知 h[L-1],h[R] ,如何求h[L...R]这一段字符串的哈希值

区间和公式: h[L...R] = h[R] - h[L-1]*P^(L-R+1)

  • 如何理解呢?

例子1:ABCDE ,现在想求h[4...5]即:DE的哈希值, 因为ABCDE与 ABC 的前三个字符值是一样,只差两位,乘上P^2,把 ABC 变为 ABC00,再用 ABCDE - ABC00 得到 DE 的哈希值

即:h[4...5] = h[5] - h[3]*p^2 -> h[L...R] = h[R] - h[L-1]*P^(L-R+1)

例子2:

//例子:
//高位-----低位
"ABCDEFGHI"
 123456789   (下标)
     L   R
字符串"A"的    哈希值为 p^0+A
字符串"AB"     哈希值为 p^1+A + p^0+B
字符串"ABC"    哈希值为 p^2+A + p^1+B + C
字符串[1,L-1]的哈希值h[L-1]p^3+A + p^2+B + p^1+C + p^0+D
字符串[1,R]  的哈希值h[R]p^8+A + p^7+B +  ...  + P^0+I
所以[L,R]字符串的哈希值为:[1...L-1]这一段的每一个数都多乘p^(R-L+1)次方,让h[L-1]h[R]的高位对齐
那么字符串从[L,R]的哈希值=h[R] - h[L - 1] * p^(R-L+1)

用途:比较不同区间的子串是否相同,就转化为对应的哈希值是否相同

  • 求一个字符串的哈希值就相当于求前缀和,求一个字符串的子串哈希值就相当于求部分和

image-20221105114340978

#include<iostream>
#include<vector>
#include<string>
using namespace std;
//使用常规的解法超出时间限制
//取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
typedef unsigned long long ULL; //使用ULL类型,代替取模2^64 , 溢出的时候就相当于是取模了
const int P = 131; //经过计算,这个数的冲突概率低//计算子串 str[l ~ r] 的哈希值
ULL get(vector<ULL>& h,vector<ULL>& p,int l,int r)
{
    return h[r] - h[l-1] * p[r-l+1]; 
}
 
int main()
{
    int n,m;
    cin >> n >> m; 
    string str;
    cin >> str;
    vector<ULL> h(n+1,0); //h[k]存储字符串前k个字母的哈希值
    vector<ULL> p(n+1,0); //p[k]存储p的多少次方 p[k] = P^k mod 2^64
    //预处理这两个数组
    p[0] = 1;//p的0次方= 1
    for(int i = 1; i <= n ;++i)
    {
        p[i] = p[i-1] * P; //
        h[i] = h[i-1] * P + str[i-1]; //预处理字符串前缀和数组
    }
    
    int l1,r1,l2,r2;
    while(m--)
    {
        cin >> l1 >> r1 >> l2 >> r2;
        if(get(h,p,l1,r1) == get(h,p,l2,r2)) 
            cout << "Yes" << endl;
        else cout << "No" << endl;
    }
    return 0;
}