[atcoder abc367f] Rearrange Query

abc367-f 题目原文

题目简述

你有两个长度为 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

输出格式
对于每次询问,输出 YesNo

数据范围
1 ≤ n , q ≤ 1 0 5 1 \le n,q \le 10^5 1n,q105
1 ≤ s 1 i , s 2 i ≤ n 1 \le s1_i,s2_i \le n 1s1i,s2in
1 ≤ l 1 , r 1 , l 2 , r 1 ≤ n 1 \le l1,r1,l2,r1 \le n 1l1,r1,l2,r1n
时限 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. 求和或求积。判断两个集合相等,最简单的就是求和或求积。错误率当然很高。
  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 1mod)要适当,太大容易卡掉,太小容易相同。

在实际过程中,用一个数组(比如 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

  1. 有人说,如果有权值,可能会爆 long long 啊。其实,如果每个元素都相同,那么,爆 long long 的值一定也相同。不过,题目可能会卡掉这样的哈希,因此我们会使用多种。
  2. rand 函数用法。首先就是要设定随机种子,通常使用时间。用的方法见上面代码。
  3. rand 函数的值在 s h o r t i n t short int shortint 范围内,如果要随机到 i n t int int 范围,可以随机出两个数,一个数乘上权值加上另一个数。
  4. 对于 3 3 3 的技巧,我们需要注意时间限制(众所周知,随机函数时间很慢)。这道题时限 2 2 2 秒,用随机函数不会炸。
  5. 这道题我使用了前缀和。因为两个数组不会改变,所以预处理两个数组的前缀和会优化时间。

5

这里我选择了 求和 和 求平方和,随机函数的范围是 1 ∼ 1 0 9 1 \sim 10^9 1109
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;
}

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值