题目简述
你有两个长度为
n
n
n 的序列
s
1
s1
s1,
s
2
s2
s2。现在有
q
q
q 次判断,每次判断
s
1
s1
s1 从
l
1
l1
l1 到
r
1
r1
r1 和
s
2
s2
s2 从
l
2
l2
l2 到
r
2
r2
r2 是否相同(忽略顺序),如果相同,输出 Yes
,否则输出 No
。
输入格式
第一行两个整数
n
n
n 和
q
q
q,表示序列长度和询问次数。
第二行
n
n
n 个整数
s
1
1
s1_1
s11 到
s
1
n
s1_n
s1n,表示
s
1
s1
s1 包含的元素。
第三行
n
n
n 个整数
s
2
1
s2_1
s21 到
s
2
n
s2_n
s2n,表示
s
2
s2
s2 包含的元素。
接下来
q
q
q 行,每行表示一个询问。每次询问四个整数,表示
l
1
l1
l1,
r
1
r1
r1,
l
2
l2
l2,
r
2
r2
r2。
输出格式
对于每次询问,输出 Yes
或 No
。
数据范围
1
≤
n
,
q
≤
1
0
5
1 \le n,q \le 10^5
1≤n,q≤105
1
≤
s
1
i
,
s
2
i
≤
n
1 \le s1_i,s2_i \le n
1≤s1i,s2i≤n
1
≤
l
1
,
r
1
,
l
2
,
r
1
≤
n
1 \le l1,r1,l2,r1 \le n
1≤l1,r1,l2,r1≤n
时限
2
2
2 秒,空间
1024
M
B
1024MB
1024MB。
样例1
输入:
5 4
1 2 3 2 4
2 3 1 4 2
1 3 1 3
1 2 3 5
1 4 2 5
1 5 1 5
输出:
Yes
No
No
Yes
解释:
第
1
1
1 次询问,
s
1
s1
s1 包含
1
,
2
,
3
1,2,3
1,2,3,
s
2
s2
s2 包含
2
,
3
,
1
2,3,1
2,3,1,相同。
第
2
2
2 次询问,
s
1
s1
s1 包含
1
,
2
1,2
1,2,
s
2
s2
s2 包含
1
,
4
,
2
1,4,2
1,4,2,不相同。
第
3
3
3 次询问,
s
1
s1
s1 包含
1
,
2
,
3
,
2
1,2,3,2
1,2,3,2,
s
2
s2
s2 包含
3
,
1
,
4
,
2
3,1,4,2
3,1,4,2,不相同。
第
4
4
4 次询问,
s
1
s1
s1 包含
1
,
2
,
3
,
2
,
4
1,2,3,2,4
1,2,3,2,4,
s
2
s2
s2 包含
2
,
3
,
1
,
4
,
2
2,3,1,4,2
2,3,1,4,2,相同。
样例2
输入:
4 4
4 4 4 4
4 4 4 4
1 2 2 3
3 3 1 1
1 3 1 4
1 4 2 3
输出:
Yes
Yes
No
No
解答
1
对于判断字符串相同,集合相同等问题,我们可以使用哈希解决。
这道题就是一个判断集合是否相同的题目,因为忽略了顺序。
哈希核心思想:我们假定某条件下,两个元素相等。因此,这个条件应尽可能精确,使得犯错的可能性越小。
2
首先,判断两个集合是否相等,有很多种方法。这里我们来说一下几种常见的类型。
由于要尽可能减少错误率,通常情况下会使用多种(至少两种)。
- 求和或求积。判断两个集合相等,最简单的就是求和或求积。错误率当然很高。
- 由于 1 1 1 错误太明显,我们可以考虑给每个数设一个权值。权值可以设为一个素数(错误率会更小),每次计算时,将结果( s u m sum sum) 乘以权值,再加上元素的值。或者,我们可以就将权值设为其本身,即求平方和。当然也可以求立方和,四次方和。
<<a 表示集合,x 表示开始位置,y 表示结束位置>>
<<p 表示权值,通常为一个较大的素数>>
<<一定不要用求积!!!>>
int hash1(int x, int y){//求和
int sum = 0;
for(int i = x; i <= y; ++ i){
sum += a[i];
}
return sum;
}
int hash2(int x, int y){//乘上权值求和
int sum = 0;
for(int i = x; i <= y; ++ i){
sum *= p;
sum += a[i];
}
}
int hash3(int x, int y){//求平方和
int sum = 0;
for(int i = x; i <= y; ++ i){
sum += a[i] * a[i];
}
return sum;
}
3
接下来的技巧都基于随机函数。
由于作者比较菜,随机函数只会用 rand。
我们可以把元素随机映射到一个桶里面。比如有 1 , 2 , 3 , 4 , 2 1,2,3,4,2 1,2,3,4,2 这几个元素,假设 1 1 1 映射到 3969 3969 3969, 2 2 2 映射到 39838449 39838449 39838449, 3 3 3 映射到 777777 777777 777777, 4 4 4 映射到 82 82 82。原数组就变为了 3969 , 39838449 , 777777 , 82 , 39838449 3969,39838449,777777,82,39838449 3969,39838449,777777,82,39838449。
由于题目的元素都 ≤ n \le n ≤n,所以预处理数组,从 1 1 1 到 n n n,每个位置取随机数。
rand 的范围(我用的是 1 ∼ m o d 1 \sim mod 1∼mod)要适当,太大容易卡掉,太小容易相同。
在实际过程中,用一个数组(比如 t t t)保存 rand 的值,计算 x x x 时只需将其对应为 t x t_x tx 就行了。
<<数组名解释见上>>
<<预处理函数>>
void solve(){
<设置随机种子>
srand(time(0));
<对于每个位置取随机数>
for(int i = 1; i <= n; ++ i){
<rand 本身就是一个随机数,模只是限定范围>
<模 mod 的范围为 0 ~ mod - 1>
<加 1 后的范围为 1 ~ mod>
t[i] = rand() % mod + 1;
}
}
4
- 有人说,如果有权值,可能会爆 long long 啊。其实,如果每个元素都相同,那么,爆 long long 的值一定也相同。不过,题目可能会卡掉这样的哈希,因此我们会使用多种。
- rand 函数用法。首先就是要设定随机种子,通常使用时间。用的方法见上面代码。
- rand 函数的值在 s h o r t i n t short int shortint 范围内,如果要随机到 i n t int int 范围,可以随机出两个数,一个数乘上权值加上另一个数。
- 对于 3 3 3 的技巧,我们需要注意时间限制(众所周知,随机函数时间很慢)。这道题时限 2 2 2 秒,用随机函数不会炸。
- 这道题我使用了前缀和。因为两个数组不会改变,所以预处理两个数组的前缀和会优化时间。
5
这里我选择了 求和 和 求平方和,随机函数的范围是
1
∼
1
0
9
1 \sim 10^9
1∼109。
用
s
u
m
1
0
sum1_0
sum10 和
s
u
m
2
0
sum2_0
sum20 表示
s
1
s1
s1 和
s
2
s2
s2 的和,用
s
u
m
1
1
sum1_1
sum11 和
s
u
m
2
1
sum2_1
sum21 表示
s
1
s1
s1 和
s
2
s2
s2 的平方和。
#include<bits/stdc++.h>
using namespace std;
int n, k;
int a[2][200010];//s1 和 s2
int p[200010];//随机映射数组
long long sum1[2][200010];
long long sum2[2][200010];
<_hash 表示 s1 从 x 到 y 的和/平方和>
<_hash2 表示 s2 从 x 到 y 的和/平方和>
long long _hash(int x, int y, int z){
long long sum = 0;
sum = sum1[z][y] - sum1[z][x - 1];
return sum;
}
long long _hash2(int x, int y, int z){
long long sum = 0;
sum = sum2[z][y] - sum2[z][x - 1];
return sum;
}
int main(){
srand(time(0));
scanf("%d %d", &n, &k);
//技巧 3
for(int i = 1; i <= n; ++ i){
p[i] = rand() % (1000000000) + 1;
}
//s1 前缀和
for(int i = 1; i <= n; ++ i){
scanf("%d", &a[0][i]);
sum1[0][i] = sum1[0][i - 1] + p[a[0][i]];
sum2[0][i] = sum2[0][i - 1] + p[a[0][i]] * p[a[0][i]];
}
//s2 前缀和
for(int i = 1; i <= n; ++ i){
scanf("%d", &a[1][i]);
sum1[1][i] = sum1[1][i - 1] + p[a[1][i]];
sum2[1][i] = sum2[1][i - 1] + p[a[1][i]] * p[a[1][i]];
}
//询问
for(int i = 1; i <= k; ++ i){
int l1, l2, r1, r2;
scanf("%d %d %d %d", &l1, &r1, &l2, &r2);
//判断
if(_hash(l1, r1, 0) == _hash(l2, r2, 1) && _hash2(l1, r1, 0) == _hash2(l2, r2, 1)){
printf("Yes\n");
}
else{
printf("No\n");
}
}
return 0;
}