题目描述:
给定一个长度为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
分析:
本题考察字符串Hash,之前写过关于字符串Hash的题解见AcWing 138 兔子与兔子。
下面再次回忆下字符串Hash的具体过程,以加深印象。
我们知道,判断两个整数是否相等的时间复杂度是O(1)的,而比较两个字符串是否相等的时间复杂度是O(n)的,如果能够将字符串都映射为一个整数,那个便可在O(1)的时间内判断字符串是否相等了。每个字符都对应一个ASCII,将单个字符映射为一个数字并不困难。而对于一个字符串,比如abc,只需要将其看作一个P进制数,求得其对应的十进制数后再对一个大的数取模即可得到该字符串经过哈希映射得到的整数了。一般而言,取P = 131或者13331,可以认为冲突的概率接近为0,对于模数可以取2^64,即取unsigned long long类型的最大表示范围,不用显示的去取模,超出表示范围的溢出部分会自动被截断,达到了只取低64位取模的目的。
例如abc,a = 1,ab = a * 131 + b = 131 + 2,abc = ab * 131 + c。由此很容易可以推出哈希数组h[i] = h[i - 1] * base + str[i] - 'a' + 1。既然h[i]表示前i个字符的hash值,那么如何求得i 到 j字符的hash值呢?与之前推导类似,h[i - 1] * p[j - i + 1]加上i到j之间的hash值即可得到h[j]的哈希值,所以i到j之间的hash值为h[j] - h[i - 1] * p[j - i + 1],为此,我们需要在求hash值的前缀和时对各个位置的权重也用p[i]存储一下,避免使用时再去计算。
#include <iostream>
#include <algorithm>
using namespace std;
typedef unsigned long long ull;
const int maxn = 100005,P = 131;
int n,m;
ull h[maxn],p[maxn];
char s[maxn];
ull get(int l,int r){
return h[r] - h[l - 1] * p[r - l + 1];
}
int main(){
cin>>n>>m;
cin>>s + 1;
h[0] = 0,p[0] = 1;
for(int i = 1;i <= n;i++){
h[i] = h[i - 1] * P + s[i];
p[i] = p[i - 1] * P;
}
int l1,r1,l2,r2;
while(m--){
cin>>l1>>r1>>l2>>r2;
cout<<(get(l1,r1) == get(l2,r2) ? "Yes" : "No")<<endl;
}
return 0;
}