CodeForce1625D最大异或集合
问题描述
给定一个数组
a[1-n]
,求解一个集合,使得集合内任意两个元素的异或值都不小于k
输入格式
第一行,包含两个整数 n , k n,k n,k
第二行,包含 n n n个整数,表示数组 a [ 1 − n ] a[1-n] a[1−n]
0 ≤ a [ i ] < 2 30 , 2 ≤ n ≤ 3 × 1 0 5 , 0 ≤ k < 2 30 0\le a[i]<2^{30}, 2\le n\le 3 \times 10^5,0\le k < 2 ^ {30} 0≤a[i]<230,2≤n≤3×105,0≤k<230
输出格式
第一行,一个整数
m
表示最大异或集合的大小第二行,
m
个整数,表示集合中的元素在a[1-n]
中的下标tags
位运算、DS、树、CF2300分
-
从高位到低位,按位考虑
- ,对于一个集合,如果当前位为
0
则将其划分给左半边的集合,反之则将其划分给右半边的集合 - 反复这个划分操作,知道当前位为
k
出现的最高位才停止划分(k
的最高位也不划分)
- ,对于一个集合,如果当前位为
-
于是,我们经过上述操作的时候,就得到若干个集合了
-
因为 n m a x = 3 × 1 0 5 n_{max}=3\times 10^5 nmax=3×105,所以最后划分出来的集合数量不会超过 2 log n 2\log n 2logn个,即 36 36 36个
-
以下我就称这些集合为:位集合
无意冒犯
bitset
-
划分所得到的位集合的性质
- 不同集合之间的两个元素异或,答案一定大于等于
k
- 显然,按照我们上面的划分方式,因为他们一定是因为在某一位
i
不同,然后被划分到不同的集合中 - 故在第
i
位,两个集合中取出的数 x , y x,y x,y,在第i
位一定是一个0
一个1
相异或得到的值,至少为 2 i 2^i 2i,因为我们的划分规则是在k
的最高位就停止,故必然有 x ⊕ y ≥ 2 i > k x\oplus y \ge 2^i > k x⊕y≥2i>k
- 显然,按照我们上面的划分方式,因为他们一定是因为在某一位
- 同一个集合中选出到最后答案里面的数,最多不超过两个
- 我们学过了一个集合内求出最大异或对的
01trie
的做法,那么我们就可以将这个集合丢进01trie
中,然后查询集合内的每一个数,若查询到a[i]
的最大异或值大于k
,则将再线性遍历一遍集合(因为需要输出的是下标,而不是数值),找出跟a[i]
配对的那个最大异或值的下标 - 这样我们就找到了这个集合里对最后答案贡献的两个数
- 那么是否有可能是三个数或者更多呢?
- 答案是不可能是,既然他们在同一个位集合内,那么就说明他们在
k
的最高位以前全部相同,只有在最高位及其以后才会出现不同。它们异或出来的结果,在k
的最高位必须为1
,这样才有可能会不小于k
,即一个数该位为0
,一个数该位为1
- 如果我们加入第三个数,鸽笼原理(抽屉原理、染色原理)得,必然存在一对该位相同,这样他们的异或值在
k
的最高位必然为0
,即必然小于k
- 故同一个集合中选出到最后答案里面的数,最多不超过两个
- 我们学过了一个集合内求出最大异或对的
- 不同集合之间的两个元素异或,答案一定大于等于
-
本题中
01trie
维护的细节- 因为我们上次插入一个集合,又删掉它们的时候,有些节点已经建立完成了,导致他们的
son
值不为0,但是不存在于当前位集合中。 - 每次用完后
trie
都是空的,但是memset
掉son
数组效率过低,所以采用delete
字符串的方式 - 所以我们采用一下方式来解决:新加入
num
数组,节点存在且num
值大于0才表示这个前缀存在于这个位集合中 - 我们需要在路径上面的点,也给它们的
num
标志加上v
,这样就能和不存在的点区分开来(num=0
),才能继续向下迭代遍历
- 因为我们上次插入一个集合,又删掉它们的时候,有些节点已经建立完成了,导致他们的
-
最后注意边界条件
k=0
时候直接输出所有数字即可 -
最后博主全部的代码大致如下
#include <bits/stdc++.h>
#include <bits/extc++.h>
#include <unordered_map>
#include <unordered_set>
using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
#define debug(x) cerr << #x << ": " << x << '\n';
#define bd cerr << "----------------------" << el;
#define el '\n'
#define cl putchar('\n');
#define pb push_back
#define eb emplace_back
#define x first
#define y second
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define lop(i, a, b) for (int i = (a); i < (b); i++)
#define dwn(i, a, b) for (int i = (a); i >= (b); i--)
#define ceil(a, b) (a + (b - 1)) / b
#define ms(a, x) memset(a, x, sizeof(a))
#define INF 0x3f3f3f3f
#define db double
#define all(x) x.begin(), x.end()
#define reps(i, x) for (int i = 0; i < x.size(); i++)
#define cmax(a, b) a = max(a, b)
#define cmin(a, b) a = min(a, b)
typedef long long LL;
typedef long double LD;
typedef pair<int, int> PII;
typedef pair<db, db> PDD;
typedef vector<int> vci;
template <typename T>
inline void read(T &x)
{
x = 0;
T f = 1;
char c = getchar();
while (!isdigit(c))
{
if(c == '-')
f = -1;
c = getchar();
}
while (isdigit(c))
{
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
x *= f;
}
constexpr int N = 3e5 + 5;
int T, n, m, k;
int a[N];
vci ans;
struct _01trie{
int son[N * 16][2];//表面一个数有30个节点
int num[N * 16]; int idx;
void build()
{
ms(son, 0);
ms(num, 0);
}
void add(int x, int v)
{
int u = 0;
dwn(i, 29, 0)
{
int &s = son[u][x >> i & 1];
if(!s) //儿子节点不存在
s = ++ idx;
u = s;
num[u] += v;
//这句话得写在里面,不能写在外面
}
}
int query(int x) //返回(x xor y)的最大值,而不是y
{
int u = 0, res = 0;
dwn(i, 29, 0)
{
int s = x >> i & 1;
if(son[u][!s] && num[son[u][!s]] > 0) //另一个儿子存在
{//说明该位异或可以为1
res += 1 << i;
u = son[u][!s];
}
else //否则说明这一位只能强制跟原本的相同
u = son[u][s];
}
return res;
}
}tr;
void work(vci v)//看一下该集合能出几个数
{//一个集合最多出两个数
if(!v.size())//v为空则没必要计算了
return ;
for(auto i : v)
tr.add(a[i], 1);//将当前集合插入
bool ok = false;//该集合是否插入两个数
for(auto i : v)
if(tr.query(a[i]) >= k) //a[i]对应的最大异或的结果>=k
{
ok = true;
for(auto j : v)
if((a[i] ^ a[j]) >= k)//找到对应的异或对
{
ans.pb(i), ans.pb(j);
break;
}
break;
}
if(!ok)//若不能插入两个数,则插入一个数
ans.pb(v[0]);
for(auto i : v)
tr.add(a[i], -1);//删除掉
return ;
}
void divide(vci v, int bit) //划分位集合
{
if( !v.size())
return ;
if(k & (1 << bit)) //到k的最高位1停止划分
{
work(v);
return ;
}
vci v0, v1;
for(auto i : v)
{
if(a[i] & (1 << bit))
v1.pb(i);//a[i]的bit位是否为1
else
v0.pb(i);
}
divide(v0, bit - 1);
divide(v1, bit - 1);
return ;
}
int main()
{
read(n), read(k);
vci v(n);
iota(v.begin(), v.end(), 1);
rep(i, 1, n)
read(a[i]);
if(k == 0)
{
cout << n << el;
rep(i, 1, n)
cout << i << ' ';
return 0;
}
divide(v, 29);
if(ans.size() <= 1)
cout << -1 << el;
else
{
cout << ans.size() << el;
for(auto it : ans)
cout << it << ' ';
}
}