模板题:https://www.acwing.com/problem/content/843/
字符串前缀哈希法,把字符串变成一个p进制数字(哈希值),实现不同的字符串映射到不同的数字。
对形如
X
1
X
2
X
3
⋯
X
n
−
1
X
n
X_1X_2X_3⋯X_{n−1}X_n
X1X2X3⋯Xn−1Xn 的字符串,采用字符的
A
S
C
I
I
ASCII
ASCII 码乘上
P
P
P 的次方来计算哈希值。
映射公式 ( X 1 × P n − 1 + X 2 × P n − 2 + ⋯ + X n − 1 × P 1 + X n × P 0 ) m o d Q (X_1×P_{n−1}+X_2×P_{n−2}+⋯+X_{n−1}×P_1+X_n×P_0)\ mod\ Q (X1×Pn−1+X2×Pn−2+⋯+Xn−1×P1+Xn×P0) mod Q
注意:
- 任意字符不可以映射成 0 0 0,否则会出现不同的字符串都映射成 0 0 0 的情况,比如 A A A, A A AA AA, A A A AAA AAA皆为 0 0 0
- 冲突问题:通过巧妙设置 P P P ( 131 131 131 或 13331 13331 13331) , Q Q Q ( 2 64 2^{64} 264)的值,一般可以理解为不产生冲突。
问题是比较不同区间的子串是否相同,就转化为对应的哈希值是否相同。
求一个字符串的哈希值就相当于求前缀和,求一个字符串的子串哈希值就相当于求部分和。
前缀和公式
h
[
i
+
1
]
=
h
[
i
]
×
P
+
s
[
i
]
i
∈
[
0
,
n
−
1
]
h[i+1]=h[i]×P+s[i] \qquad i∈[0,n−1]
h[i+1]=h[i]×P+s[i]i∈[0,n−1]
h
h
h 为前缀和数组,
s
s
s 为字符串数组
区间和公式
h
[
l
,
r
]
=
h
[
r
]
−
h
[
l
−
1
]
×
P
r
−
l
+
1
h[l,r]=h[r]−h[l−1]×P_{r−l+1}
h[l,r]=h[r]−h[l−1]×Pr−l+1
区间和公式的理解:
ABCDE 与 ABC 的前三个字符值是一样,只差两位,
乘上
P
2
P_2
P2 把 ABC 变为 ABC00,再用 ABCDE - ABC00 得到 DE 的哈希值。
h
[
r
]
−
h
[
l
−
1
]
h[r] - h[l-1]
h[r]−h[l−1] 这里
h
[
r
]
h[r]
h[r] 的最高位是
P
r
−
1
P^{r-1}
Pr−1 ,
h
[
l
−
1
]
h[l-1]
h[l−1] 的最高位是
P
l
−
2
P^{l-2}
Pl−2 要对齐来作差就要乘上
P
r
−
l
+
1
P^{r-l+1}
Pr−l+1
#include <cstdio>
using namespace std;
const int N = 100010,P = 131;
// 用unsigned long long 刚好是2的64次,溢出相当于取模
typedef unsigned long long ULL;
int n,m;
char str[N];
ULL h[N],p[N];
// h[i]前i个字符的hash值
// 字符串变成一个p进制数字,体现了字符+顺序,需要确保不同的字符串对应不同的数字
// P = 131 或 13331 Q=2^64,在99%的情况下不会出现冲突
// 使用场景: 两个字符串的子串是否相同
ULL get(int l,int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
scanf("%d%d",&n,&m);
scanf("%s",str+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;
}
while(m--)
{
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
if(get(l1,r1) == get(l2,r2)) printf("Yes\n");
else printf("No\n");
}
return 0;
}