AcWing 841. 字符串哈希(哈希表)
作者:互联网
题目链接 :点击查看
题目描述 :
给定一个长度为 n 的字符串,再给定 m 个询问,每个询问包含四个整数 l1,r1,l2,r2,请你判断 [l1,r1] 和 [l2,r2]这两个区间所包含的字符串子串是否完全相同。字符串中只包含大小写英文字母和数字。
输入输出格式 :
输入
第一行包含整数 n 和 m,表示字符串长度和询问次数。
第二行包含一个长度为 n 的字符串,字符串中只包含大小写英文字母和数字。
接下来 m 行,每行包含四个整数 l1,r1,l2,r2,表示一次询问所涉及的两个区间。
注意,字符串的位置从 1 开始编号。
输出
对于每个询问输出一个结果,如果两个字符串子串完全相同则输出
Yes
,否则输出No
。每个结果占一行。
输入输出样例 :
输入
8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2
输出
Yes
No
Yes
题目分析 :
字符串哈希本质就是将字符串的前缀和转换为P进制整数的形式,对于每一个前缀,其转换成为的P进制整数即为此前缀字符串的哈希值。因此我们要对比两子串是否相同,只需要比较两子串的hash值即可。那么怎么将字符串转化成为P进制整数呢?比如字符串"abcd"我们可以把其拆成四位字符'a' 'b' 'c' 'd', 以 a为高位,因为每个字符都有相应的ASCII码,因此字符串的hash值为‘’ASCII('a') * p ^ 3 + ASCII('b') * p ^ 2 + ASCII('c') * p ^ 1 + ASCII('d') * p ^ 0。由前面的前缀和知识可以推知,以第i位字符为结尾的前缀其哈希值h[i] = h[i - 1] * P + str[i];(h[i]为整型数组,这里str[i]会自动转换为相应字符的ASCII码)。初始化完字符串每个前缀的哈希值,然后我们来看看怎么计算字符串l ~ r区间子串的hash值,由之前的前缀和知识可知,若要计算l ~ r 的前缀和,算出s[r] - s[l - 1]即可,计算l~r的hash值可以类比于此。在之前初始化的过程,我们已经计算出了h[r]的值与h[l - 1]的值,但是h[l, r]并不是二者直接相减,且看上述字符串"abcd"其中 a是高位,d为最低位,所以对于h[r]与h[l - 1]其中的h[0]反倒是哈希值的高位,又由前缀和可知,两者的高位相同,所以由于h[l - 1]值比h[r]的值短,我们要先将h[l - 1]与h[r]中数高位(左端)进行对齐, 具体做法为将h[l - 1]向左移动r - l + 1位。比如十进制数"1230“与“10”其中第一个数中的1与第二个数的1都是表示千位,所以将“10” 乘 “100”得“1000”,"1230"再与“1000”相减。此操作的代码为 h[r] - h[l - 1] * P ^ (l - r + 1)。为了方便上述的移位操作,我们在初始化h数组时同时用p数组记录P的n次幂,即p[i] = P ^ i = p[i - 1] * P,且p[0] = 1,所以P^ (l - r + 1) = p[ l - r + 1]。特别地,由于hash问题常常伴随hash冲突,在这里P可以取131或13331,而且每一次求得的hash值为了保证范围(在某一区间),都要进行取模运算,即将hash{i] mod Q, 由经验值可知,P取131||13331 且 Q取 2 ^ 64时,哈希冲突的概率最小,此时可以忽略此问题。关于将hash[i]取模问题,在这里我们可以将h数组定义为unsigned long long类型,如若hash值大于等于2^64直接从0开始,相当于取模功能。详见如下代码。
代码 :
#include<iostream>
#include<cstdio>
using namespace std;
typedef unsigned long long ULL;//减少2^64取模环节
const int N = 1e5 + 7, P = 131;//经验值
int n, m;
char str[N];
ULL h[N], p[N];
ULL get(int l, int r) {
return h[r] - h[l - 1] * p[r - l + 1];//将h[l - 1]与h[r]左端对齐后再进行相减,即为当前字符串的哈希值
}
int main() {
cin >> n >> m;
cin >> str + 1; //下标从1开始
p[0] = 1;
for (int i = 1; i <= n; i ++ ) {
h[i] = h[i - 1] * P + str[i];
p[i] = p[i - 1] * P; //p的每个位置,都存相应的p^i值,方便之后的移位运算
}
while (m -- ) {
int l1, r1, l2, r2;
cin >> l1 >> r1 >> l2 >> r2;
if (get(l1, r1) == get(l2, r2)) cout << "Yes" << endl;
else cout << "No" << endl;
}
return 0;
}
-----------------------------------------------------------------------------------
下面给出字符串哈希的相关模板
核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64
// 初始化
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];
}
标签:hash,前缀,841,int,long,哈希,字符串,AcWing 来源: https://blog.csdn.net/m0_51111980/article/details/117132022