AcWing 841. 字符串哈希

\(AcWing\) \(841\). 字符串哈希

一、题目描述

给定一个长度为 \(n\) 的字符串,再给定 \(m\) 个询问,每个询问包含四个整数 \(l_1,r_1,l_2,r_2\),请你判断 \([l_1,r_1]\)\([l_2,r_2]\) 这两个区间所包含的字符串子串是否完全相同。

字符串中只包含大小写英文字母和数字。

输入格式
第一行包含整数 \(n\)\(m\),表示字符串长度和询问次数。

第二行包含一个长度为 \(n\) 的字符串,字符串中只包含大小写英文字母和数字。

接下来 \(m\) 行,每行包含四个整数 \(l_1,r_1,l_2,r_2\),表示一次询问所涉及的两个区间。

注意,字符串的位置从 \(1\) 开始编号。

输出格式
对于每个询问输出一个结果,如果两个字符串子串完全相同则输出 Yes,否则输出 No

每个结果占一行。

数据范围
\(1≤n,m≤10^5\)

输入样例:

8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2

输出样例:

Yes
No
Yes

二、算法思路

字符串哈希 \(O(n+m)\)

全称 字符串前缀哈希法,把字符串变成一个\(p\)进制数字(哈希值),实现不同的字符串映射到不同的数字。并且,用\(h[N]\)记录字符串前\(N\)个字符的\(Hash\)值,类似于前缀和。

作用就是把\(O(N)\)的时间复杂度降为\(O(1)\)。比如本题就是对比任意两段内字符串是不是相同,正常就是类似于一个循环长度次的\(substr\),其实用\(hash\)差就能一步搞定!

举个栗子

str="ABCABCDEYXCACWING";
h[0]=0;
h[1]="A"的Hash值;
h[2]="AB"的Hash值;
h[3]="ABC"的Hash值;
h[4]="ABCA"的Hash值;

对形如\(X_1X_2X_3...X_{n-1}X_n\)的字符串,采用字符 \(ASCII\)码乘上\(P\)次方来计算哈希值。

映射公式:\((X_1 \times P^{n-1} +X_2 \times P^{n-2}+...+X_{n-1}\times P^1 + X_n \times P^0) mod \ Q\)

比如:字符串\(ABCD\)\(P=131\)
那么\(h[4]=65*131^3+66*131^2+67*131^1+68*131^0\)

\(AB\)\(P=131\)
说是\(h[2]=65*131^1+66*131^0\)

我们想要求"\(CD\)"的\(HASH\)值,怎么求呢?
就是 \(h[4]-h[2]*131^2\)

注意

  1. 任意字符不可以映射成\(0\),否则会出现不同的字符串都映射成\(0\)的情况,比如\(A\),\(AA\),\(AAA\)皆为\(0\)

  2. 冲突问题:通过巧妙设置\(P\)(\(131\)\(13331\)) ,\(Q\)(\(2^{64}\))的值,一般可以理解为不产生冲突(玄学)。

\(Q=2^{64}\)这个取模动作在代码中没有出现过,这是因为采用了$unsinged \ long\ long $,它本身就是\(2^{64}\),如果超过了这个数字,就直接自动溢出,起到了取模的作用。

问题是比较不同区间的子串是否相同,就转化为对应的哈希值是否相同。

求一个字符串的哈希值就类似于构建一维前缀和,求一个字符串的子串哈希值就相当于一维前缀和应用:

构建: \(h[i]=h[i-1] \times P+s[i-1] \ \ \ i∈[1,n]\) \(h\)为前缀和数组,\(s[i-1]\)为字符串数组此位置字符对应的\(ASCII\)码。

应用: 查询\(l,r\)之间部分字符串的\(hash=h[r]−h[l−1]×P^{r−l+1}\)

三、实现代码

#include <bits/stdc++.h>

using namespace std;
typedef unsigned long long ULL;

const int N = 100010;
const int P = 131;
int n, m;
string str;
ULL h[N], p[N];
ULL get(int l, int r) {
    return h[r] - h[l - 1] * p[r - l + 1];
}
int main() {
    cin >> n >> m >> str;
    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]; // 前缀和
    }
    while (m--) {
        int l1, r1, l2, r2;
        cin >> l1 >> r1 >> l2 >> r2;
        if (get(l1, r1) == get(l2, r2))
            puts("Yes");
        else
            puts("No");
    }
    return 0;
}
posted @ 2021-09-16 13:59  糖豆爸爸  阅读(409)  评论(2编辑  收藏  举报
Live2D