字符串哈希详解 二维哈希 Hash

字符串hash方法

字符串 h a s h hash hash 就是把一个字符串映射成一个非负整数。

(同时碰撞的概率极低)

设计一个较大质数 p p p,把字符串看作 p p p 进制数。(如 31 31 31 131 131 131 1331 1331 1331

(想想为什么要用质数)

对于已知的字符串 s s s,我们有 h a s h hash hash 值为 H ( s ) \text{H}(s) H(s),那么我们在 s s s 后面增加一个字符 c c c 构成新的字符串 s + c s+c s+c,该串的 h a s h hash hash 值则为 H ( s ) ∗ p + ( c − ′ a ′ + 1 ) \text{H}(s)*p+(c-'a'+1) H(s)p+(ca+1)

如我们取 p = 131 p=131 p=131,对于字符串 ′ a b a c ′ 'abac' abac p p p 进制数为 ( 1   2   1   3 ) p (1~2~1~3)_p (1 2 1 3)p,即 h a s h hash hash 值为 1 ∗ p 3 + 2 ∗ p 2 + 1 ∗ p + 3 1*p^3+2*p^2+1*p+3 1p3+2p2+1p+3。此时我们的字符串更新为 ′ a b a c d ′ 'abacd' abacd,那么我们的 ′ a b a c ′ 'abac' abac 需要左移一位, p p p 进制数变为 ( 1   2   1   3   0 ) p (1~2~1~3~0)_p (1 2 1 3 0)p,再加上字符 ‘ d ‘ `d` d 的值 ( ′ d ′ − ′ a ′ + 1 ) ('d'-'a'+1) (da+1) p p p 进制数最后为 ( 1   2   1   3   4 ) p (1~2~1~3~4)_p (1 2 1 3 4)p h a s h hash hash 值则为 H ( ′ a b a c ′ ) ∗ p + ( ′ d ′ − ′ a ′ + 1 ) \text{H}('abac')*p+('d'-'a'+1) H(abac)p+(da+1)

  • h a s h hash hash:使用两个质数作为 p 1 p_1 p1 p 2 p_2 p2,计算两个 h a s h hash hash 值,当两个 h a s h hash hash 值分别相等,表示两个字符串相等,这种方法碰撞概率更小
取子串hash值

所以对于一个字符串,我们需要取他的子串时,例如,对于字符串 s s s,我们需要取其子串 s l , r s_{l,r} sl,r h a s h hash hash 值,我们可以用 H ( s 0 , r ) − H ( s 0 , l − 1 ) ∗ p r − l + 1 \text{H}(s_{0,r})-\text{H}(s_{0,l-1})*p^{r-l+1} H(s0,r)H(s0,l1)prl+1

s 0 , r = ′ a b a c ′ s_{0,r}='abac' s0,r=abac s 0 , l − 1 = ′ a b ′ s_{0,l-1}='ab' s0,l1=ab,所以 H ( s 0 , r ) = ( 1   2   1   3 ) p \text{H}(s_{0,r})=(1~2~1~3)_p H(s0,r)=(1 2 1 3)p H ( s 0 , l − 1 ) = ( 1   2 ) p \text{H}(s_{0,l-1})=(1~2)_p H(s0,l1)=(1 2)p,所以对于 s l , r = ′ a c ′ s_{l,r}='ac' sl,r=ac,先将 H ( s 0 , l − 1 ) = ( 1   2 ) p \text{H}(s_{0,l-1})=(1~2)_p H(s0,l1)=(1 2)p 向左移 l e n g t h ( s l , r ) length(s_{l,r}) length(sl,r) 位,变成 H ( s 0 , l − 1 ) ∗ p 2 = ( 1   2   0   0 ) p \text{H}(s_{0,l-1})*p^2=(1~2~0~0)_p H(s0,l1)p2=(1 2 0 0)p,最后 H ( s 0 , r ) − H ( s 0 , l − 1 ) ∗ p 2 = ( 1   3 ) p = H ( ′ a c ′ ) = H ( s l , r ) \text{H}(s_{0,r})-\text{H}(s_{0,l-1})*p^2=(1~3)_p=\text{H}('ac')=\text{H}(s_{l,r}) H(s0,r)H(s0,l1)p2=(1 3)p=H(ac)=H(sl,r)

溢出与取模

p p p 很大且字符串很长时,字符串的 h a s h hash hash 值也会很大,这时候一般需要设计一个值 m m m 对字符串的 h a s h hash hash 值进行取模,有 2 2 2 种处理方法:

  • 使用 2 64 2^{64} 264 进行取模,即直接使用 u n s i g n e d   l o n g   l o n g unsigned~long~long unsigned long long 储存 h a s h hash hash 值,产生溢出时相当于自动对 2 64 2^{64} 264 进行取模
  • 使用一个大质数进行取模

总体来说,字符串 h a s h hash hash很难构造能卡的数据,相对来说第一个和双 h a s h hash hash碰撞概率更小一些,且由于不需要取模,第一个方法常数会小一点。

code

// acwing 兔子与兔子
#include <bits/stdc++.h>
// using namespace std;

#define ull unsigned long long
#define p 131

int n;
char s[1000005];
ull b[1000005];
ull hash[1000005];

int main() {
    
    
    b[0] = 1; // p的0次方
    hash[0] = 0;
    scanf("%s", s);
    int len = strlen(s);
    for (int i = 1; i <= len + 3; i++) {
    
    
        b[i] = b[i - 1] * p;
        hash[i] = hash[i - 1] * p + (s[i - 1] - 'a' + 1);
    }
    scanf("%d", &n);
    for (int i = 0, l1, l2, r1, r2; i < n; i++) {
    
    
        scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
        ull s1 = hash[r1] - hash[l1 - 1] * b[r1 - l1 + 1];
        ull s2 = hash[r2] - hash[l2 - 1] * b[r2 - l2 + 1];
        if (s1 == s2) {
    
    
            puts("YES");
        } else
            puts("NO");
    }
    return 0;
}

二维哈希

可以类比二维前缀和,注意横向 h a s h hash hash 和纵向 h a s h hash hash 所用的 p p p 值不要相同。

code

// UVa 11019
#include <bits/stdc++.h>
using namespace std;

#define ull unsigned long long
#define p1 131
#define p2 1331

int n, m, x, y;
char s1[1005][1005], s2[105][105];
ull h[1005][1005], h2[105][105], b1[1005], b2[1005];

void solve() {
    
    
    int ans = 0;
    scanf("%d%d", &n, &m);
    getchar();
    for (int i = 1; i <= n; i++) scanf("%s", s1[i]);
    scanf("%d%d", &x, &y);
    getchar();
    for (int i = 1; i <= x; i++) scanf("%s", s2[i]);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            h[i][j] = h[i][j - 1] * p1 + (int)(s1[i][j - 1] - 'a' + 1);
    for (int j = 1; j <= m; j++)
        for (int i = 1; i <= n; i++) h[i][j] += h[i - 1][j] * p2;
    for (int i = 1; i <= x; i++)
        for (int j = 1; j <= y; j++)
            h2[i][j] = h2[i][j - 1] * p1 + (int)(s2[i][j - 1] - 'a' + 1);
    for (int j = 1; j <= y; j++)
        for (int i = 1; i <= x; i++) h2[i][j] += h2[i - 1][j] * p2;
    ull val = h2[x][y];
    // printf("# %llu #\n", val);
    for (int i = x; i <= n; i++) {
    
    
        for (int j = y; j <= m; j++) {
    
    
            ull tmp = h[i][j] - h[i - x][j] * b2[x] - h[i][j - y] * b1[y] +
                      h[i - x][j - y] * b1[y] * b2[x];
            if (tmp == val) ++ans;
        }
    }
    printf("%d\n", ans);
}

int main() {
    
    
    b1[0] = b2[0] = 1;
    for (int i = 1; i < 1000 + 3; i++) {
    
    
        b1[i] = b1[i - 1] * p1;
        b2[i] = b2[i - 1] * p2;
    }
    int t;
    scanf("%d", &t);
    while (t--) solve();
    return 0;
}

参考《算法竞赛进阶指南》

字符串hash

hdu 1711

poj 1200

poj 3461

二维hash

UVa 11019

猜你喜欢

转载自blog.csdn.net/qq_46144509/article/details/122346906