蓝桥杯2665选数异或64分

文章描述了一种解决数列中查询两个数异或等于特定值x的方法。首先,提出了朴素的遍历方法会超时,然后通过利用异或运算性质优化,使用map存储每个数的下标,并构建区间。通过二分查找来快速响应查询。最后,尝试使用动态规划优化,但仍然未通过所有测试用例。
摘要由CSDN通过智能技术生成

题目描述

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

输入格式

输入的第一行包含三个整数 n, m, x 。

第二行包含 n 个整数 A1, A2, · · · , An 。

接下来 m 行,每行包含两个整数 li ,ri 表示询问区间 [li ,ri ] 。

输出格式

对于每个询问, 如果该区间内存在两个数的异或为 x 则输出 yes, 否则输出 no。

样例输入

4 4 1
1 2 3 4
1 4
1 2
2 3
3 3

样例输出

yes
no
yes
no

提示

显然整个数列中只有 2, 3 的异或为 1。

对于 20% 的评测用例,1 ≤ n, m ≤ 100;

对于 40% 的评测用例,1 ≤ n, m ≤ 1000;

对于所有评测用例,1 ≤ n, m ≤ 100000 ,0 ≤ x < 220 ,1 ≤ li ≤ ri ≤ n , 0 ≤ Ai < 220。 

蓝桥杯2022年第十三届省赛真题-选数异或 - C语言网 

基于朴素的想法,对于区间进行遍历,这样肯定是超时的

引入一个tip,a^b= c-> a^c=b且 b^c=a

那么对于arr[i],我们可以很容易确定应该找哪个数w,使得arr[i]^w=x -> w = arr[i]^x

数组中如果存在很多w呢?

考虑下面这段序列

....w....w.....w.....w.....arr[i].....w......w.....w

其实我们只需要关注两个w,即离arr[i]最近的两个

思路:

遍历arr,使用map存储每一个arr[j]的下标j,对于arr[i](i>=j),arr[i]^x存在,那么其一定被存在map之中

很容易就得到一个区间 (map[arr[i]^x],i)

再考虑下面这个序列

....w....w.....w.....w.....arr[i]....arr[k]....w......w.....w  , (arr[i]==arr[k])

遇到第一个w,存下标,接着第二个第三个第四个,都要更新w的下标(找最近,肯定要更新的啊),遍历到arr[i], 发现map.count(w),那我们就得到一个序列(idx_w4, i), 接着遍历到arr[k],这时候有必要存(idx_w4, k)吗?其实不需要对于任意询问 lr ,如果 (idx_w4, k) 满足 ,那么 (idx_w4, i)必满足

再考虑这样一个序列

....w....w.....w......q......w.....arr[i]....arr[k].....arr[e].....w......w.....w  , (arr[i]==arr[k], arr[e] ==q )

遍历到arr[e] 有必要存  (idx_q, e)吗?也是不需要!!理由同上

那么我们会发现,最后我们标记的区间满足这样(ai,aj)....(ak,al).....(ao,ap)

有ai...ak....ao和aj....al...ap均是递增的,即所有区间的左端点和右端点是严格递增的

那么用二分很容易得到每次询问lr是否包含某个区间

查找左端点中>= l 的第一个下标a

右端点中<= r 的第一个下标b

if(a!=-1&&b!=-1&&a<=b)那么说明包含某个区间

代码如下

#include<iostream>
#include<vector>
#include <numeric>
#include<set>
#include <queue>
#include <unordered_map> 
#include<unordered_set>
#include<math.h> 
#include<algorithm>
#include<stack>
#include<string>
#define PI acos(-1) // 1 2 3 5 6 7
using namespace std;
typedef long long ll;
const ll INF = -1;
const ll mod = 1e9 + 7;  
vector<int>v;
vector<pair<int, int>>dis;
int n, m, x;
int low(int val) { //找第一个>=的
	int l = 0;
	int r = dis.size() - 1;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (dis[mid].first < val)l = mid + 1;
		else r = mid - 1;
	}
	return l == dis.size() ? -1 : l;
}
int up(int val) { //最后一个<=的
	int l = 0;
	int r = dis.size() - 1;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (dis[mid].second > val)r = mid - 1;
		else l = mid + 1;
	}
	return r >= 0 ? r : -1;
}
int main() { 
	cin >> n >> m >> x;
	v.resize(n);
	for (int i = 0; i < n; i++) {
		cin >> v[i];
	} 
	unordered_map<int, int>map;
	for (int i = 0; i < n; i++) {
		map[v[i]] = i; //存入map
		int pre = v[i] ^ x;  // 目标 w 
		if (map.count(pre) && map[pre] != -1) { // 谈到的两种情况
			if (dis.size() == 0 || map[pre] > dis.back().first) {
				dis.push_back({ map[pre],i });
			}
			map[pre] = -1; //不管有没有新增区间,都把pre置-1,因为如果添加了区间,这个pre不会在用到,如果没有添加,说明这个pre太靠前了,更不可能用到
		}
	}
	int l, r;
	for (int i = 0; i < m; i++) {
		cin >> l >> r;
		int a = low(l - 1);
		int b = up(r - 1); 
		if (a != -1 && b != -1 && a <= b)cout << "yes";
		else cout << "no";
		if (i != m - 1)cout << endl;
	}
}

时间复杂度:预处理需要O(n),查询O(mlogn)总时间:O(mlogn)

信心满满提交了,md wa了,64分

后面又想到了动态规划

令dp[i]表示1~i中左端点最靠右的值,即左端点最大的数

例如以下序列,相同字母表示a^A=x

qerwERWQ

那么dp[E]=2,dp[R]=3,dp[W]=4,dp[Q]=4

虽然q位于第一位,w位于第四位,而dp[i]不代表i对应的左端点最大,而是1~i左端点的最大

那么对于一次询问LR,只要判断dp[R]>=L?即可

#include<iostream>
#include<vector>
#include <numeric>
#include<set>
#include <queue>
#include <unordered_map> 
#include<unordered_set>
#include<math.h> 
#include<algorithm>
#include<stack>
#include<string>
#define PI acos(-1) // 1 2 3 5 6 7
using namespace std;
typedef long long ll;
const ll INF = -1;
const ll mod = 1e9 + 7;   
vector<int>dp;//dp[i]表示 0~i 中左端点的最大值
int n, m, x; 
int main() {  
	cin >> n >> m >> x; 
	dp.resize(n + 1, -1);
	unordered_map<int, int>map;  
	int temp;
	for (int i = 1; i <= n; i++) {
		cin >> temp;
		map[temp] = i;
		int pre = temp ^ x; 
		if (map.count(pre))dp[i] = max(dp[i - 1], map[pre]);
		else dp[i] = dp[i - 1];
	}
	int l, r;
	for (int i = 0; i < m; i++) {
		cin >> l >> r;
		if (dp[r] >= l)cout << "yes";
		else cout << "no";
		if (i != m - 1)cout << endl;
	}
}

时间复杂度O(n)

呵呵,又挂了,64分,很迷

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值