@@ author: 明月清了个风
@@ first publish: 2024.12.4
字符串哈希和上一篇的整数哈希一样,通过将字符串映射到一个数字来表示该字符串,只是对于字符串来说,这个哈希函数映射的方法会更特殊。
基本的思想是通过将字符串中的每个字符映射到一个数字,通常使用ASCII码值,通过加权求和的方式计算出该字符串的哈希值。
假设字符串 S S S为 S = s 0 s 1 s 2 . . . s n − 1 S = s_0 s_1 s_2 ... s_{n-1} S=s0s1s2...sn−1,则其哈希值可以通过以下多项式进行计算:
h ( S ) = ( s 0 ⋅ p n − 1 + s 1 ⋅ p n − 2 + s 2 ⋅ p n − 3 + . . . + s n − 1 ⋅ p 0 ) m o d Q (1) h(S) = (s_0 \cdot p^{n - 1} + s_1 \cdot p^{n - 2} + s_2 \cdot p^{n - 3} + ... + s_{n-1} \cdot p^0 ) \; mod \; Q \tag{1} h(S)=(s0⋅pn−1+s1⋅pn−2+s2⋅pn−3+...+sn−1⋅p0)modQ(1)
其中:
a
的值)unsigned long long
的范围,这样数值越界就可以直接当做取模了。字符串哈希的目的就是为了能够快速判断子串的性质,比如是否存在,那如何快速求出子串的哈希值就很关键。
对于字符串 S S S,我们通过前缀哈希构造一个哈希数组h[]
,其中h[i]
表示字符串 S S S从第1
个字符到第i
个字符的哈希值,哈希值可以由以下公式得到:
h [ i ] = ( h [ i − 1 ] × p + s i ) m o d Q (2) h[i] = (h[i - 1] \times p + s_i) mod Q \tag{2} h[i]=(h[i−1]×p+si)modQ(2)
式中各值含义与上述相同,其中h[0] = 0
,表示空字符串的哈希值。
那么我们要获取区间[l, r]
子串的哈希值就可以通过类似前缀和的思想进行计算,利用以下公式:
h ( s [ l , r ] ) = ( h [ r ] − h [ l − 1 ] ∗ p r − l + 1 ) m o d Q (3) h(s[l, r]) = (h[r] - h[l - 1] * p^{r-l+1}) mod Q \tag{3} h(s[l,r])=(h[r]−h[l−1]∗pr−l+1)modQ(3)
解释:每个从第1
个字符开始的子串都被当预处理成了一个p
进制的数,那么对于子串 s [ 1 , r ] s[1, r] s[1,r]和 s [ 1 , l − 1 ] s[1,l - 1] s[1,l−1]来说,他们转化为10进制数后为:
D e c ( s [ 1 , r ] ) = s 1 ⋅ p r − 1 + s 2 ⋅ p r − 2 + s 3 ⋅ p r − 3 + . . . + s r ⋅ p 0 (4) Dec(s[1, r]) = s_1 \cdot p^{r - 1} + s_2 \cdot p^{r - 2} + s_3 \cdot p^{r - 3} + ... + s_{r} \cdot p^0 \tag{4} Dec(s[1,r])=s1⋅pr−1+s2⋅pr−2+s3⋅pr−3+...+sr⋅p0(4)
D e c ( s [ 1 , l − 1 ] ) = s 1 ⋅ p l − 2 + s 2 ⋅ p l − 3 + s 3 ⋅ p l − 4 + . . . + s l − 1 ⋅ p 0 (5) Dec(s[1, l - 1]) = s_1 \cdot p^{l - 2} + s_2 \cdot p^{l - 3} + s_3 \cdot p^{l - 4} + ... + s_{l - 1} \cdot p^0 \tag{5} Dec(s[1,l−1])=s1⋅pl−2+s2⋅pl−3+s3⋅pl−4+...+sl−1⋅p0(5)
可以看出,两者的公共部分子串 s [ 1 , l − 1 ] s[1, l - 1] s[1,l−1]差了 p r − l + 1 p^{r - l + 1} pr−l+1倍,因此,式(5)乘上 p r − l + 1 p^{r - l + 1} pr−l+1就相当于将两者的 s [ 1 , l − 1 ] s[1, l - 1] s[1,l−1]部分对齐,再将式(4)-式(5)即可得式(3)括号部分。
[原题链接](841. 字符串哈希 - AcWing题库)
给定一个长度为 n n n个字符串,再给定 m m m个询问,每个询问包含四个整数 l 1 , r 1 , l 2 , r 2 l_1,r_1,l_2,r_2 l1,r1,l2,r2,请你判断 [ l 1 , r 1 ] [l_1, r_1] [l1,r1]和 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]这两个区间所包含的字符串子串是否完全相同,字符串中只包含大小写英文字母和数字。
第一行包含整数 n n n和 m m m,表示字符串长度和询问次数。
第二行包含一个长度为 n n n的字符串,字符串中只包含大小写英文字母和数字。
接下来 m m m行,每行包含四个整数 l 1 , r 1 , l 2 , r 2 l_1,r_1,l_2,r_2 l1,r1,l2,r2,表示一次询问所涉及的两个区间。
注意,字符串的位置从 1 1 1开始编号。
对于每个询问指令输出一个结果,如果两个字符串子串完全相同则输出Yes
,否则输出No
.
1 ≤ n , m ≤ 1 0 5 1 \leq n,m \leq 10^{5} 1≤n,m≤105,
#include
#include
using namespace std;
typedef unsigned long long ULL;
const int N = 100010, P = 131;
char str[N];
int n, m;
ULL h[N], p[N];
ULL gets(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
cin >> n >> m >> str + 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];
}
while(m --)
{
int l1, r1, l2, r2;
cin >> l1 >> r1 >> l2 >> r2;
if(gets(l1, r1) == gets(l2, r2)) cout << "Yes" << endl;
else cout << "No" << endl;
}
}