思维转换 - 区间最值,小题两则

E - Prefix Equality

题意
给定长度为 n 的两个数列 A 和 B,给出 m 次询问。
每次询问给定两个数 x 和 y,问 A 数列 1~x 位置构成的集合 是否和 B 数列 1~y 位置构成的集合相同?
(集合中不考虑元素顺序和重复元素)

1 ≤ N , Q ≤ 2 × 1 0 5 1 \leq N, Q \leq 2 \times 10^{5} 1N,Q2×105
1 ≤ a i , b i ≤ 1 0 9 1 \leq a_{i}, b_{i} \leq 10^{9} 1ai,bi109 1 ≤ x i , y i ≤ N 1 \leq x_{i}, y_{i} \leq N 1xi,yiN

思路
一开始想着直接用set搞,每个位置一个set,当前位置的set是上一位置的set插入当前数,但是时间复杂度太高了。
然后就一直想着如何判断两个集合相等。。最后也没想出来。

其实可以转化一下思维:
这道题只是问两个集合想不想等,如果能够发现一个集合中的数没有在另一个集合中出现,那么这两个集合一定不相等。如果没有这样的数,那就是相等的。
又观察到每次询问都是从 1 位置到后面一位置,所以可以预处理出 A 数组中的每一个元素首次在 B 数组中出现的位置,记为 f1[i]。(如果没有出现,记为一个很大的数)
询问的时候取区间 1-x 中 f1[i] 的最大值,如果发现最大值(A数组集合中元素首次在 B 数组中出现的位置)大于询问的 B 数组的位置 y,那么就说明 B 数组的询问集合中一定没有最大值对应的元素。
同样处理判断 B 数组。

这样就转化为了区间最值问题,每次询问查询区间最值O(logn)。

Code

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N], b[N];
int f1[N], f2[N];
int max1[N][30], max2[N][30];

void init()
{
	for(int i=1;i<=n;i++) max1[i][0] = f1[i], max2[i][0] = f2[i];
	
	int t = log(n)/log(2);
	for(int j=1;j<=t;j++)
	{
		for(int i=1;i<=n-(1<<j)+1;i++)
		{
			max1[i][j] = max(max1[i][j-1], max1[i+(1<<j-1)][j-1]);
			max2[i][j] = max(max2[i][j-1], max2[i+(1<<j-1)][j-1]);
		}
	}
}

int query1(int l, int r)
{
	int t = log(r-l+1)/log(2);
	return max(max1[l][t], max1[r-(1<<t)+1][t]);
}
int query2(int l, int r)
{
	int t = log(r-l+1)/log(2);
	return max(max2[l][t], max2[r-(1<<t)+1][t]);
}


signed main(){
	Ios;
	cin >> n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		if(!mp1.count(a[i])) mp1[a[i]] = i;
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
		if(!mp2.count(b[i])) mp2[b[i]] = i;
	}
	
	for(int i=1;i<=n;i++)
	{
		f1[i] = mp2[a[i]];
		if(!f1[i]) f1[i] = 1e9;
		f2[i] = mp1[b[i]];
		if(!f2[i]) f2[i] = 1e9;
	}
	
	init();
	
	cin >> m;
	while(m--)
	{
		int x, y;cin>>x>>y;
		if(query1(1, x) > y || query2(1, y) > x) cout << "No\n";
		else cout << "Yes\n";
	}
	
	return 0;
}

2022[蓝桥杯A组] 选数异或

题意
给定一个长度为 n n n 的数列 A A A,和一个非负整数 x x x
给定 m m m 次查询, 每次询问能否从某个区间 [ l , r ] [l, r] [l,r] 中选择两个数使得他们的异或等于 x x x

思路
这道题中异或的地方需要转化一下:
如果两个数 a 和 b 的异或值为 x,即:a ^ b = x
由异或的性质,也就相当于:a ^ x = b, b ^ x = a
如果知道其中两个值的话,另外一个值便是确定的了。
所以可以遍历数列中的每个位置,找出其对应的异或等于 x 的另一值。

考虑如果一个位置 i 在所询问的区间 [l, r] 中,那么它肯定想要后面位置中,其对应的另一值的位置尽量小。尽可能小才能尽可能的在询问区间中。

所以可以预处理出所有数,其后面位置中第一次出现的对应数的位置 pos[i]
对于查询的区间 [l, r]:

  • 如果区间中的 pos[i] 最小值小于询问的右端点 r,说明这段区间中存在两个数可以异或成 x,满足。
  • 否则不满足。

于是就转化为了区间最值问题。

Code

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define int long long

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N], b[N];
int mp[2000010], f[N];
int mina[N][30];

void init()
{
	int t = log(n)/log(2);
	for(int i=1;i<=n;i++) mina[i][0] = f[i];
	
	for(int j=1;j<=t;j++)
	{
		for(int i=1;i<=n-(1<<j)+1;i++)
		{
			mina[i][j] = min(mina[i][j-1], mina[i+(1<<j-1)][j-1]);
		}
	}
}

int query(int l, int r)
{
	int t = log(r-l+1)/log(2);
	return min(mina[l][t], mina[r-(1<<t)+1][t]);
}

signed main(){
	Ios;
	int x;
	cin >> n >> m >> x;
	
	for(int i=1;i<=n;i++)
	{
		cin >> a[i];
		b[i] = a[i]^x;
	}
	
	for(int i=n;i>=1;i--)
	{
		f[i] = mp[b[i]];
		if(!f[i]) f[i] = 1e9;
		mp[a[i]] = i;
	}
	
	init();
	
	while(m--)
	{
		int l, r;cin >> l >> r;
		if(query(l, r) <= r) cout << "yes\n";
		else cout << "no\n";
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值