题目大意
给定
n
(
2
≤
n
≤
3
×
1
0
5
)
n(2 \le n \le 3\times10^5)
n(2≤n≤3×105)个数
a
i
(
0
≤
k
≤
2
30
−
1
)
)
a_i(0 \le k \le 2^{30}-1))
ai(0≤k≤230−1))和自然数
k
(
0
≤
a
i
≤
2
30
−
1
)
k(0 \le a_i \le 2^{30}-1)
k(0≤ai≤230−1),挑出尽可能多的数,并且使得它们的疑惑和不小于
k
k
k,输出数量和挑选的数。
题目链接
思路
首先需要推出一个非常重要的结论:
从 n n n个数中挑出两个数,使得它们的异或和最小,那么这两个数一定是把这 n n n个数排序后相邻的数。
证明:假设有三个数 a < b < c a < b < c a<b<c:
-
a ⊕ c < b ⊕ c a \oplus c < b \oplus c a⊕c<b⊕c,那么我们比较一下 b , c b,c b,c,二进制下从最高位往低位找,出现的第一个不同的位数,因为 a < b < c a < b < c a<b<c,所以这一位肯定是 c c c这一位为 1 1 1, a , b a,b a,b这一位为 0 0 0。所以即可推出 a ⊕ b < a ⊕ c a \oplus b < a \oplus c a⊕b<a⊕c
-
a ⊕ c > b ⊕ c a \oplus c > b \oplus c a⊕c>b⊕c,我们只需要比较一下$ a \oplus b,b \oplus c$即可,最小值还是出现在相邻的数中的。
有了这个结论后,我们就可以继续做了,我们把 n n n个数从小到大排序后,从小到大遍历。假如我们已经找到了一个集合 s s s,我们遍历到了一个新的数 a i a_i ai,想要判断它能否放入这个集合中,只需判断 a i ⊕ m a x ( s ) ≥ k a_i \oplus max(s) \ge k ai⊕max(s)≥k即可,与集合中的其他数没有了关系。
因此,我们设
d
p
i
dp_i
dpi表示以
a
i
a_i
ai为最大值的集合中最多有多少个数,
可以得到状态转移方程
d
p
j
=
m
a
x
(
d
p
i
)
+
1
,
dp_j = max(dp_i) + 1,
dpj=max(dpi)+1, 其中
a
j
⊕
a
i
≥
k
a_j \oplus a_i \ge k
aj⊕ai≥k。
同时题目让我们输出每个被选中的数,所以我们在更新 d p j dp_j dpj时,要记录下它的值是从哪里继承来的 p r e [ j ] = i pre[j] = i pre[j]=i。
但是,直接进行状态转移的时间复杂度是 O ( n 2 ) O(n^2) O(n2),会超时,由于是异或操作,我们想到用 t r i e trie trie树来进行加速。
从最高位开始往树中插入。
我们遍历到了
a
i
a_i
ai,那么为了找到异或和大于
k
k
k的数,我们需要这么寻找:
设二进制下
a
i
a_i
ai这一位为
b
i
t
bit
bit,root为当前节点。
-
如果这一位 k k k为 1 1 1,那么我们只能往 t r i e [ r o o t ] [ b i t ⊕ 1 ] trie[root][bit\oplus 1] trie[root][bit⊕1]
-
如果这一位 k k k为 0 0 0,那么我们有两种选择,选择 t r i e [ r o o t ] [ b i t ⊕ 1 ] trie[root][bit\oplus 1] trie[root][bit⊕1],那么异或和就一定大于 k k k了,所以就没必要继续了,直接更新答案。或者选择 t r i e [ r o o t ] [ b i t ] trie[root][bit] trie[root][bit],继续往下寻找更新答案。
更新完 d p i dp_i dpi后,我们把 a i a_i ai插入树中,同时我们需要更新 f [ r o o t ] f[root] f[root],表示经过这个点的数中,集合最大的数是多少。
代码
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxN = 3e5 + 5;
struct Num {
int val, index;
}a[maxN];
bool cmp(Num x, Num y)
{
return x.val < y.val;
}
int n, k, pre[maxN * 31], trie[maxN * 31][2], f[maxN * 31], d[maxN * 31], cnt = 1;
int main()
{
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i].val);
a[i].index = i;
}
sort(a + 1, a + 1 + n, cmp);
for(int i = 1; i <= n; ++i) {
int ans = 0, root = 1;
for(int j = 30; j >= 0 && root; --j) {
int bit = (a[i].val >> j) & 1, rev = bit ^ 1;;
if((k >> j) & 1)
root = trie[root][rev];
else {
if(d[ans] < d[f[trie[root][rev]]])
ans = f[trie[root][rev]];
root = trie[root][bit];
}
if(j == 0 && d[ans] < d[f[root]])
ans = f[root];
}
d[i] = d[ans] + 1; pre[i] = ans; root = 1;
for(int j = 30; j >= 0; j--) {
int bit = (a[i].val >> j) & 1;
if(!trie[root][bit])
trie[root][bit] = ++cnt;
root = trie[root][bit];
if(d[f[root]] < d[i])
f[root] = i;
}
}
int ans = 1;
for(int i = 2; i <= n; ++i)
if(d[ans] < d[i])
ans = i;
if(d[ans] < 2)
printf("-1\n");
else {
printf("%d\n", d[ans]);
for(int i = ans; i != 0; i = pre[i])
printf("%d ", a[i].index);
}
return 0;
}