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}
1≤N,Q≤2×105
1
≤
a
i
,
b
i
≤
1
0
9
1 \leq a_{i}, b_{i} \leq 10^{9}
1≤ai,bi≤109,
1
≤
x
i
,
y
i
≤
N
1 \leq x_{i}, y_{i} \leq N
1≤xi,yi≤N
思路
一开始想着直接用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;
}