【算法学习笔记】7:字符串前缀哈希法

字符串前缀哈希法

有很多字符串的问题可以用字符串哈希来做,不一定要用KMP算法。

这里的哈希方式是一些比较特殊的哈希方式,即字符串前缀哈希。比如有一个字符串abcde,则可以先预处理出来所有前缀的哈希,比如h[1]就表示a的哈希,h[2]就表示ab的哈希,特别的,定义h[0]=0​表示前0个字符的哈希值为0

要定义某一个前缀的哈希值,只要把字符串看成是一个 P P P进制的数,那么每一位的ASCII码就表示这一位的数字是多少。那么上面的例子的字符串哈希值(即对应的十进制的数值)为:
a × P 4 + b × P 3 + c × P 2 + d × P 1 + e × P 0 a \times P^4 + b \times P^3 + c \times P^2 + d \times P^1 + e \times P^0 a×P4+b×P3+c×P2+d×P1+e×P0

转化成数字之后这个数字可能非常大,所以要取模一下,一般是对 2 64 2^{64} 264取模,在C++里就只需要直接用unsigned long long来存储就行了,就不需要真正的写取模这个动作了。

需要注意:

  1. 对字符串的每一位字符都不应该映射成0​,假如把一个字符A映射成0了,那么AA映射过来就也是0,就不对了。
  2. 在哈希数字的时候可能会存在冲突,这个字符串哈希方法是不处理冲突的,经验上一般取 P = 131 P=131 P=131或者 P = 13331 P=13331 P=13331

使用这种方式,可以利用前缀哈希来求得每一个子串的哈希值,如果要求字符串下标从LR的哈希值,只需要知道1..L-1的哈希值h[L-1]1..R的哈希值h[R]。其实要计算的L..R的哈希值就是这段数的 P P P进制表示,所以其实就是在 P P P进制的意义上把1..L-1这个数左移到和1..R对齐,然后再用1..R的值给它减掉就行了。

因为取模运算对加减乘都是同态的,所以用取模后的结果来计算,再取模得到的结果也一定是正确的:
H a s h [ L . . R ] = ( H a s h [ R ] − H a s h [ L − 1 ] × P R − L + 1 + 2 64 )    m o d    2 64 Hash[L..R] = (Hash[R] - Hash[L-1] \times P^{R-L+1} + 2^{64}) \ \ mod \ \ 2^{64} Hash[L..R]=(Hash[R]Hash[L1]×PRL+1+264)  mod  264

因为直接用unsigned long long来存,所以这里的加模再取模也是没必要做的了。

模板题

AcWing 841 字符串哈希

#include <iostream>

using namespace std;

typedef unsigned long long ULL;

const int N = 1e5 + 10;
const int P = 131; // 表示成P进制数

ULL h[N], p[N]; // 哈希值和P的次幂
char str[N]; // 字符串,从1开始标号

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的次幂和哈希值
    p[0] = 1;
    for (int i = 1; i <= n; i ++ ) {
        p[i] = p[i - 1] * P;
        h[i] = h[i - 1] * P + str[i];
    }
    
    // 处理m此询问
    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;
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值