【题目】
题目描述:
有q次操作,每次操作是以下两种:
1、 加入一个数到集合中
2、 查询,查询当前数字与集合中的数字的最大异或值,最大 and 值,最大 or 值
输入格式:
第一行 1 个正整数 Q 表示操作次数
接下来 Q 行,每行 2 个数字,第一个数字是操作序号 OP(1 , 2),第二个数字是 X 表示操作的数字
输出格式:
输出查询次数行,每行3 个整数,空格隔开,分别表示最大异或值,最大 and 值,最大 or 值
样例数据:
【样例1】
输入
5 1 2 1 3 2 4 1 5 2 7
输出
7 0 7 5 5 7
解释
询问 4 时,已插入 2、3,最大异或值为4^3=7,最大 and 值为4&3或4&2=0,最大 or 值为4|3=7
询问 7 时,已插入 2、3、5,最大异或值为7^2=5,最大 and 值为7&5=5,最大 or 值为7|2=7|3=7|5=7
【样例2】
输入
10 1 194570 1 202332 1 802413 2 234800 1 1011194 2 1021030 2 715144 2 720841 1 7684 2 85165
输出
1026909 201744 1032061 879724 984162 1048062 655316 682376 1043962 649621 683464 1048571 926039 85160 1011199
备注:
对于 10% 的数据保证 1 ≤ Q ≤ 5000
对于另 10% 的数据保证 X < 1024
对于另 40% 的数据保证 1 ≤ Q ≤ 100000
对于所有数据保证 1 ≤ Q ≤ 1000000 , 1 ≤ X ≤ ,保证第一个操作为 1 操作。
【分析】
先考虑异或,由于异或是相同为 0,不同为 1,我们就以集合中的数(换算成二进制)构建Trie树,找的时候从高位向低位找,每次都尽量往与自己不同的数走(比如 0 就走 1,1 就走 0),实在不行才往相同的数走,这样保证答案最大
接下来是与和或,它们本质上差不多,这里就细讲一下与。那么我们还是从高位向低位贪心,由于与是都为 1 才为 1,所以当某一位为 0 时,既可以填 1,又可以填 0,这个时候我们只需要标记子集,默认填 0 即可
【代码】
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1<<19+5
using namespace std;
int t,trie[N][2];
bool sub[N];
void mark(int x)
{
int i;
sub[x]=true;
for(i=0;i<20;++i)
if((x>>i&1)&&(!sub[x^(1<<i)]))
mark(x^(1<<i));
}
void insert(int x)
{
int i,k,p=0;
for(i=19;~i;--i)
{
k=(x>>i&1?1:0);
if(!trie[p][k])
trie[p][k]=++t;
p=trie[p][k];
}
}
int askxor(int x)
{
int i,k,p=0,now=0;
for(i=19;~i;--i)
{
k=(x>>i&1?0:1);
if(trie[p][k])
{
p=trie[p][k];
if(k) now|=1<<i;
}
else
{
p=trie[p][k^1];
if(k^1) now|=1<<i;
}
}
return now;
}
int askand(int x)
{
int i,now=0;
for(i=19;~i;--i)
if((x>>i&1)&&sub[now|1<<i])
now|=1<<i;
return now;
}
int askor(int x)
{
int i,now=0;
for(i=19;~i;--i)
if((!(x&(1<<i)))&&sub[now|1<<i])
now|=1<<i;
return now;
}
int main()
{
// freopen("xorand.in","r",stdin);
// freopen("xorand.out","w",stdout);
int n,i,s,x;
scanf("%d",&n);
for(i=1;i<=n;++i)
{
scanf("%d%d",&s,&x);
if(s==1)
{
mark(x);
insert(x);
}
else
{
printf("%d ",askxor(x)^x);
printf("%d ",askand(x)&x);
printf("%d\n",askor(x)|x);
}
}
// fclose(stdin);
// fclose(stdout);
return 0;
}