题目描述:
很久很久以前,森林里住着一群兔子。有一天,兔子们想要研究自己的 DNA 序列。
我们首先选取一个好长好长的 DNA 序列(小兔子是外星生物,DNA 序列可能包含 26 个小写英文字母)。然后我们每次选择两个区间,询问如果用两个区间里的 DNA 序列分别生产出来两只兔子,这两个兔子是否一模一样。注意两个兔子一模一样只可能是他们的 DNA 序列一模一样。
输入格式
第一行输入一个 DNA 字符串 S。第二行一个数字 m,表示 m 次询问。接下来 m 行,每行四个数字 l1,r1,l2,r2,分别表示此次询问的两个区间,注意字符串的位置从1开始编号。
输出格式
对于每次询问,输出一行表示结果。如果两只兔子完全相同输出 Yes,否则输出 No(注意大小写)。
数据范围
1≤length(S),m≤1000000
输入样例:
aabbaabb
3
1 3 5 7
1 3 6 8
1 2 1 2
输出样例:
Yes
No
Yes
分析:
本题考察字符串Hash,字符串Hash是指将一个任意长度的字符串映射为一个非负整数,并且冲突的概率几乎为0.
一般而言,我们将字符串看作P进制数,将每个字符都视为一个大于0的数值。比如,对于由小写字母构成的字符串可以令a = 1,b = 2,...,z = 26.取固定值M,求出该P进制数对M的余数即为该字符串的Hash值。一般而言,我们取P = 131或13331,此时冲突概率会很低,所以一般认为,Hash值相等,就可以认为字符串相等。为了避免取模运算,M一般取2^64,恰好是unsigned long long类型的最大表示范围,在计算时不用特殊处理溢出,溢出时就相当于自动取模了。
如何在O(n)的时间内构建出字符串的哈希映射呢?例如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]存储一下,避免使用时再去计算。
于是,构建好hash表后,本题就可以在O(1)的时间内得到l到r范围内字符串的hash值并与另一子串的hash值对比判断是否相等了。
#include <iostream>
#include <cstring>
using namespace std;
typedef unsigned long long ull;
const int maxn = 1000010,base = 131;
char str[maxn];
ull h[maxn],p[maxn];
ull get(int l,int r){
return h[r] - h[l - 1] * p[r - l + 1];
}
int main(){
scanf("%s",str + 1);
int n = strlen(str + 1);
p[0] = 1;
for(int i = 1;i <= n;i++){
h[i] = h[i - 1] * base + str[i] - 'a' + 1;
p[i] = p[i - 1] * base;
}
int m;
cin>>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;
}