题目描述
给定一个长度为 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。
基于朴素的想法,对于区间进行遍历,这样肯定是超时的
引入一个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分,很迷