You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

124 lines
3.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##[$AcWing$ $841$. 字符串哈希](https://www.acwing.com/problem/content/843/)
### 一、题目描述
给定一个长度为 $n$ 的字符串,再给定 $m$ 个询问,每个询问包含四个整数 $l_1,r_1,l_2,r_2$,请你判断 $[l_1,r_1]$ 和 $[l_2,r_2]$ 这两个区间所包含的字符串子串是否完全相同。
字符串中只包含大小写英文字母和数字。
**输入格式**
第一行包含整数 $n$ 和 $m$,表示字符串长度和询问次数。
第二行包含一个长度为 $n$ 的字符串,字符串中只包含大小写英文字母和数字。
接下来 $m$ 行,每行包含四个整数 $l_1,r_1,l_2,r_2$,表示一次询问所涉及的两个区间。
注意,字符串的位置从 $1$ 开始编号。
**输出格式**
对于每个询问输出一个结果,如果两个字符串子串完全相同则输出 `Yes`,否则输出 `No`
每个结果占一行。
**数据范围**
$1≤n,m≤10^5$
**输入样例:**
```cpp {.line-numbers}
8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2
```
**输出样例:**
```cpp {.line-numbers}
Yes
No
Yes
```
### 二、算法思路
字符串哈希 $O(n+m)$
全称 **字符串前缀哈希法**,把字符串变成一个$p$进制数字(哈希值),实现不同的字符串映射到不同的数字。并且,用$h[N]$记录字符串前$N$个字符的$Hash$值,类似于前缀和。
作用就是把$O(N)$的时间复杂度降为$O(1)$。比如本题就是对比任意两段内字符串是不是相同,正常就是类似于一个循环长度次的$substr$,其实用$hash$差就能一步搞定!
**举个栗子**
```c++
str="ABCABCDEYXCACWING";
h[0]=0;
h[1]="A"的Hash值;
h[2]="AB"的Hash值;
h[3]="ABC"的Hash值;
h[4]="ABCA"的Hash值;
```
对形如$X_1X_2X_3...X_{n-1}X_n$的字符串,采用字符 $ASCII$码乘上$P$次方来计算哈希值。
映射公式:$(X_1 \times P^{n-1} +X_2 \times P^{n-2}+...+X_{n-1}\times P^1 + X_n \times P^0) mod \ Q$
比如:字符串$ABCD$$P=131$
那么$h[4]=65*131^3+66*131^2+67*131^1+68*131^0$
而$AB$$P=131$
说是$h[2]=65*131^1+66*131^0$
我们想要求"$CD$"的$HASH$值,怎么求呢?
就是 $h[4]-h[2]*131^2$
#### 注意
1. 任意字符不可以映射成$0$,否则会出现不同的字符串都映射成$0$的情况,比如$A$,$AA$,$AAA$皆为$0$
2. 冲突问题:通过巧妙设置$P$($131$或$13331$) ,$Q$($2^{64}$)的值,一般可以**理解为不产生冲突**(玄学)。
$Q=2^{64}$这个取模动作在代码中没有出现过,这是因为采用了$unsinged \ long\ long $,它本身就是$2^{64}$,如果超过了这个数字,就直接自动溢出,起到了取模的作用。
问题是比较不同区间的子串是否相同,就转化为对应的哈希值是否相同。
求一个字符串的哈希值就类似于构建一维前缀和,求一个字符串的子串哈希值就相当于一维前缀和应用:
构建: $h[i]=h[i-1] \times P+s[i-1] \ \ \ i∈[1,n]$ $h$为前缀和数组,$s[i-1]$为字符串数组此位置字符对应的$ASCII$码。
应用: 查询$l,r$之间部分字符串的$hash=h[r]h[l1]×P^{rl+1}$
![](https://img2022.cnblogs.com/blog/8562/202202/8562-20220218204832745-1678863560.png)
### 三、实现代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const int N = 100010;
const int P = 131;
int n, m;
string str;
ULL h[N], p[N];
ULL get(int l, int r) {
return h[r] - h[l - 1] * p[r - l + 1];
}
int main() {
cin >> n >> m >> str;
p[0] = 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 - 1]; // 前缀和
}
while (m--) {
int l1, r1, l2, r2;
cin >> l1 >> r1 >> l2 >> r2;
if (get(l1, r1) == get(l2, r2))
puts("Yes");
else
puts("No");
}
return 0;
}
```