【写在前面】
子集:概念同集合里的子集。举例 { 1,2,3 }的子集有{ 1 },{ 2 },{ 3 },{ 1,2 },{ 1,3 },{ 2,3 },{ 1,2,3 },{ 空 }
超集:包含原集合中所有数的集合。举例 { 1,2,3 }的超集有{ 1,2,3,4 },{ 1,2,3,4,5 },{ 1,2,3,6 }……
位运算
描述
有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
【输出样例1】
7 0 7
5 5 7
【样例解释1】
询问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
【输出样例2】
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<=2^20 保证第一个操作为1操作。
分析
首先这是一道位运算的题,那我们就要从二进制去思考
异或 :不同为1,相同为0
与:同为1才为1
或:有一个为1就为1
咱一个一个的来思考
【异或】对于给的数 x ,我们要在集合中找一个数使得他们异或的值最大,那么按位贪心即可,从最高位开始
具体这么实现呢?我们借助 trie 树,把集合中的数按二进制存在 trie 树上。要异或的时候,就从root开始往下找,如果它的儿子中有与 x 的这一位相反的话,就往这个儿子下面去搜,然后这样搞完即可
【与】与 怎么弄呢?由于同为 1 才为1,我们对于 x 数在二进制下的 0 不做处理,而当这一位为 1 的时候,我们就去找对应位上是否有1,有的话就记录下来,没有的话就不管。
具体这样搞:我们标记集合中每一个数的子集,然后判断 与 ,由于我们原数为0的那一位,既可以填0也可以填1,我们就默认为0,那这样搞出来会包含集合中的某一个数
【或】或 和 与的做法基本一致。对于原数 x 中为 1 的那一位我们不管,只考虑为 0 的那一位即可
代码
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<iostream>
#include<queue>
#define N 2100009
#define M 405003
#define inf 0x7fffffff
#define in read()
#define ll long long
using namespace std;
inline int read(){
char ch;int f=1,res=0;
while((ch=getchar())<'0'||ch>'9')
if(ch=='-') f=-1;
while(ch>='0'&&ch<='9'){
res=(res<<3)+(res<<1)+ch-'0';
ch=getchar();
}
return f==1?res:-res;
}
int q,trie[N][2],root=0,fl[N],tot=0;
inline void mark(int x){//标记子集
fl[x]=1;
for(int i=19;i>=0;--i)
if(((1<<i)&x)&&fl[(1<<i)^x]==0)
fl[1<<i]=1,mark((1<<i)^x);
}
inline void add(int x){//构建 trie树
int pos=root,c;
for(int i=19;i>=0;--i){
if((1<<i)&x) c=1;
else c=0;
if(!trie[pos][c]) trie[pos][c]=++tot;//
pos=trie[pos][c];
}
}
inline int askyi(int x){
int pos=root,c,ans=0;
for(int i=19;i>=0;--i){
if((1<<i)&x) c=1;
else c=0;
if(trie[pos][1-c]) pos=trie[pos][1-c],ans=ans|(1<<i);
//如果有和x相反的值,这位上就应该为1,最后直接输出即可
else pos=trie[pos][c];
if(pos==0) return ans;
}
return ans;
}
inline int askand(int x){
int ans=0;
for(int i=19;i>=0;--i)
if((1<<i)&x&&fl[ans+(1<<i)]) ans=ans|(1<<i);
return ans;
}
inline int askhuo(int x){
int ans=0;
for(int i=19;i>=0;--i)
if((((1<<i)&x)==0)&&fl[ans+(1<<i)]) ans=ans|(1<<i);
return ans;
}
int main(){
q=in;
int i,j,k;
for(i=1;i<=q;++i){
int op=in,x=in;
if(op==1){
mark(x);//&和| 有用
add(x);//^有用
}
else{
printf("%d ",askyi(x));
printf("%d ",askand(x));
printf("%d \n",x|askhuo(x));
}
}
return 0;
}