【2018/08/21】T2-trie树+贪心-位运算(SDOJ 3772)

【写在前面】

子集:概念同集合里的子集。举例 { 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;
}

 

    原文作者:Trie树
    原文地址: https://blog.csdn.net/weixin_42557561/article/details/81909948
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞