NOI2024 集合 题解

给个链接:集合

很神秘的题目。基本上看到之后就可以想到哈希。

首先想到一个比较神秘的暴力。就是对于每个询问,扫一遍所有 \(a\) 中的数出现的位置,把它弄成一个哈希值(具体怎么弄随意)存到 set 里,然后看看是不是和 \(b\) 中的数出现的位置这样操作后的集合完全相等。事实上就是判断是否对于所有在 \(a\) 中这个区间内出现的数 \(x\),都存在一个在 \(b\) 中出现的不同的数 \(y\),使得 \(x,y\) 出现的位置完全相同。这样做应该是有 \(70\) 分的,但是显然不够。

然后我们考虑一个事情,对于每一个 \(i\)\([i,j]\) 这个区间合法在 \(j\) 尽可能小的时候最有可能成立。换句话说有单调性,可以二分。

但是,二分是没有必要的,我们可以用双指针做的更好。因为,如果 \([i,j]\) 合法,\([i+1,j]\) 存在,那么 \([i+1,j]\) 合法。

所以我们对每个 \(i\) 找出最靠右的 \(j\) 且满足 \([i,j]\) 合法,然后就可以 \(O(1)\) 回答询问。时间复杂度 \(O(n+q)\)

这里为了保险,把每个位置的值也做了哈希,用 \(p_i\) 存储。

给个代码:

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define N 600005
using namespace std;
int n,m,q,a[N][3],b[N][3];
ull nowa[N],nowb[N],suma,sumb,p[N];
int ri[N];//使[i,j]合法的最靠右的j
ull get_rnd(ull x){
	return x*x*x;//随便变换一下
}
void work(int id,int type){
	for(int j=0;j<3;j++){
		ull i=a[id][j];
		suma-=get_rnd(nowa[i]);//把原来的值减掉
		nowa[i]+=type*p[id];//看情况加上或减去这个位置的哈希值
		suma+=get_rnd(nowa[i]);//加上现在的值
	}
	for(int j=0;j<3;j++){//这里同理
		ull i=b[id][j];
		sumb-=get_rnd(nowb[i]);
		nowb[i]+=type*p[id];
		sumb+=get_rnd(nowb[i]);
	}
}
signed main(){
	srand(time(0));
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++){
		for(int j=0;j<3;j++){
			cin>>a[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=0;j<3;j++){
			cin>>b[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		ri[i]=n;
	}
	for(int i=1;i<=n;i++){
		p[i]=rand()*rand()+923;
	}
	work(1,1);
	for(int i=1,j=1;i<=n;i++){
		while(j<=n&&suma==sumb){//如果当前仍然合法
			j++;
			if(j>n)break;
			work(j,1);
		}
		ri[i]=min(ri[i],j-1);
		work(i,-1);//i要右移,所以撤掉这一位的贡献
	}
	while(q--){
		int l,r;
		cin>>l>>r;
		cout<<(ri[l]>=r?"Yes\n":"No\n");
	}
	return 0;
}
posted @ 2024-07-21 12:24  zxh923  阅读(71)  评论(0编辑  收藏  举报