AcWing - 字符串哈希(字符串查询)

题目链接:https://www.acwing.com/problem/content/description/843/
时/空限制:1s / 64MB

题目描述

给定一个长度为n的字符串,再给定m个询问,每个询问包含四个整数l1,r1,l2,r2,请你判断[l1,r1]和[l2,r2]这两个区间所包含的字符串子串是否完全相同。

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

输入格式

第一行包含整数n和m,表示字符串长度和询问次数。

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

接下来m行,每行包含四个整数l1,r1,l2,r2,表示一次询问所涉及的两个区间。

注意,字符串的位置从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

解题思路

题意:判断[l1,r1]和[l2,r2]这两个区间所包含的字符串子串是否完全相同。
思路:将字符串看成P进制数(判断两个整数是否相等好判断),P的经验值是131或13331,取这两个值的冲突概率低。

根据定义分别求出hash[i]:

  • hash[1]=s1
  • hash[2]=s1∗p+s2
  • hash[3]=s1∗p^2+s2∗p+s3
  • hash[4]=s1∗p^3+s2∗p^2+s3∗p+s4
  • hash[5]=s1∗p^4+s2∗p^3+s3∗p^2+s4∗p+s5

现在我们想求s3s4的hash值,不难得出为s3∗p+s4,并且从上面观察,如果看hash[4]−hash[2]并将结果中带有s1,s2系数的项全部消掉,就是所求。但是由于p的阶数,不能直接消掉,所以问题就转化成,将hash[2]乘一个关于p的系数,在做差的时候将多余项消除,从而得到结果。

不难发现,对应项系数只差一个p^2,而4-3+1=2(待求hash子串下标相减再加一),这样就不难推导出来此例题的求解式子。hash[4]−hash[2]∗p^(4−2+1)
至此,通过对上例的归纳,可以得出如下的公式:
若已知一个|S|=n的字符串的hash值,hash[i],1≤i≤n,其子串sl..sr,1≤l≤r≤n对应的hash值为:

  • hash=hash[r]−hash[l−1]∗p^(r−l+1)

考虑到hash[i]每次对p取模,进一步得到下面的式子:hash=(hash[r]−hash[l−1]∗p^(r−l+1))%MOD。
注意到括号里面有可能是负数,故:hash=((hash[r]−hash[l−1]∗p^(r−l+1))%MOD+MOD)%MOD。
至此得到求子串hash值公式。取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果。

Accepted Code:

/* 
 * @Author: lzyws739307453 
 * @Language: C++ 
 */
#include <bits/stdc++.h>
using namespace std;
const int P = 131;
const int MAXN = 1e5 + 5;
typedef unsigned long long ULL;
ULL h[MAXN], p[MAXN]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64
char str[MAXN];
// 初始化
void init(int n) {
    p[0] = 1;
    for (int i = 1; i <= n; i ++ ) {
        h[i] = h[i - 1] * P + str[i];
        p[i] = p[i - 1] * P;
    }
}
// 计算子串 str[l ~ r] 的哈希值
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);
    init(n);
    while (m--) {
        int l1, r1, l2, r2;
        scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
        if (get(l1, r1) != get(l2, r2))
            printf("No\n");
        else printf("Yes\n");
    }
    return 0;
}
展开阅读全文
©️2020 CSDN 皮肤主题: 代码科技 设计师: Amelia_0503 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值