Codeforces Round #765 (Div. 2)D题
Codeforces Round #765 (Div. 2)D题
题意
给定一个集合\(S=\{a_1,a_2,\dots,a_n\}\)。给定一个数\(k\),找出\(S\)的一个最大的子集\(T\),满足对于任意的\(a,b\in T\),都有\(a\oplus b\geq k\)。
数据范围满足
\(2\leq n\leq 3\times 10^5\)
\(0\leq a_i, k\leq2^{30}-1\)
若不存在满足题意的子集\(T\),输出\(-1\)。
若存在,输出两行,第一行包含一个整数\(l\),表示子集\(T\)的大小,第二行包含\(l\)个整数,表示该子集中的每个元素在集合\(S\)中的下标。
分析
如果没有任何的特殊性质,这就是一个一般图的最大团问题,是个NP-Hard问题。所以这道题只能去挖掘性质。
如果存在\(3\)个互不相同的数\(a,b,c\),不妨设\(a<b<c\),尝试探寻一下\(a\oplus b, b\oplus c, a\oplus c\)之间的大小关系。
由于\(a<b<c\),故其二进制表示只能是如下两种形式(x
表示该位相同,?
表示该位任意)
a:xxxx...x0???...?????...
b:xxxx...x1xxx...x0???...
c:xxxx...x1xxx...x1???...
a^b:0000...01???...?????...
b^c:0000...00000...01???...
a^c:0000...01???...?????...
或者
a:xxxx...x0xxx...x0???...
b:xxxx...x0xxx...x1???...
c:xxxx...x1???...?????...
a^b:0000...00000...01???...
b^c:0000...01???...?????...
a^c:0000...01???...?????...
发现不论是上面的哪种情况都有\(a\oplus c \geq \min(a\oplus b, b\oplus c)\)
故只要满足\(a\leq b\leq c\)(加上等号,显然\(a\oplus c \geq \min(a\oplus b, b\oplus c)\)也成立),就可以由\(a\oplus b\geq k\)且\(b\oplus c \geq k\),推出\(a\oplus c \geq k\)。
所以,我们可以先给集合\(S\)升序排序,令\(f_i\)为满足题意的,最大值小于等于\(a_i\)的最大子集的大小,利用上面的性质,可以很容易写出转移方程,时间复杂度为\(O(n^2)\)。
然而对于\(n\)的范围来讲,这个复杂度还是太高了,考虑优化。由于问题与异或相关,所以可以考虑字典树,把状态定义在字典树上,令\(f_i\)为满足题意的,最大值的前缀为字典树上\(i\)号结点所表示的前缀的最大子集的大小。从小到大依次插入集合\(S\)中的元素,每次在插入时更新对应的链上的\(f\),这条链上的\(f\)的更新源自满足与当前插入元素异或大于等于\(k\)的前缀的对应结点,故每次更新只需要\(O(\log a_{max})\),所以总体时间复杂度为\(O(n\log a_{\max})\)
代码
#include <algorithm>
#include <cstdio>
#include <utility>
using namespace std;
typedef pair<int, int> Pii;
const int maxn = 3e5 + 10;
const int maxm = 4e6 + 10;
const int dep = 30;
Pii dp[maxm], a[maxn];
int n, k, fa[maxn], tot, ch[maxm][2];
Pii query(int t) {
Pii res = {0, 0};
int u = 0;
// t^?>=k
// 在某一位上
// t=0,k=0: 1 可行; 0 继续;
// t=0,k=1: 1 继续; 0 不行;
// t=1,k=0: 1 继续; 0 可行;
// t=1,k=1: 1 不行; 0 继续;
// 总结:每次只需向t^k儿子继续
// 若k=0,查看有无!(t^k)儿子,若有更新res
for (int i = dep - 1; i >= 0; i--) {
int v = ((t ^ k) >> i) & 1;
if (!((k >> i) & 1) && ch[u][!v]) {
res = max(res, dp[ch[u][!v]]);
}
if (!ch[u][v])
return res;
u = ch[u][v];
}
// 如果一直继续到底,说明t^?=k,?满足条件,更新res
res = max(res, dp[u]);
return res;
}
void insert(int t, Pii p) {
int u = 0;
for (int i = dep - 1; i >= 0; i--) {
int v = (t >> i) & 1;
if (!ch[u][v])
ch[u][v] = ++tot;
dp[u] = max(dp[u], p);
u = ch[u][v];
}
dp[u] = max(dp[u], p);
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i].first);
a[i].second = i;
}
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i++) {
Pii res = query(a[i].first);
fa[a[i].second] = res.second;
res.second = a[i].second;
res.first++;
insert(a[i].first, res);
}
if (dp[0].first >= 2) {
printf("%d\n", dp[0].first);
for (int i = dp[0].second; i; i = fa[i]) {
printf("%d ", i);
}
puts("");
} else {
puts("-1");
}
return 0;
}