NOIP模拟 位运算(trie树+按位贪心)

【题目描述】

有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

【输入样例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操作。

对于10%的数据,显然暴力模拟即可,每一次询问就扫一遍当前数组并更新最大^,&,|值即可,复杂度O(q^2)。

对于20%的数据,在10%的基础上标记出现过的的数,复杂度O(q*c)。

对于100%的数据,显然异或可用 trie 树解决,&和|显然可以按位贪心,问题就是&时,若 x该位已为零,则既可以填1,又可以填0,于是只需要标记子集,默认填零即可,|时类似。 

贴代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1<<20;

int le[MAXN];

void markless(int x)
{
	le[x]=1;
	for(int i=0;i<20;++i)
	{
		if((x>>i&1)&&!le[x^(1<<i)])
		  markless(x^(1<<i));
	}
}

int root=1,tot=1,trans[(1<<21)+10][2];
int q,a;
void insert(int x)
{
	int p=root;
	for(int i=19;~i;--i)
	{
		int c=(x>>i&1?1:0);
		if(!trans[p][c])
		  trans[p][c]=++tot;
		p=trans[p][c];
	}
}

int ask(int x)
{
	int p=root,now=0;
	for(int i=19;~i;--i)
	{
		int c=(x>>i&1?0:1);
		if(trans[p][c])
	    {
	    	p=trans[p][c];
	    	if(c)
	    	  now|=1<<i;
	    }
	    else
	    {
	    	p=trans[p][c^1];
	    	if(c^1)
	    	  now|=1<<i;
	    }
	}
	return now;
}

int main()
{
	scanf("%d",&q);
	while(q--)
	{
		int tp,x;
		scanf("%d%d",&tp,&x);
		if(tp==1)
		{
			markless(x);
			insert(x);
		}
		else
		{
			printf("%d ",ask(x)^x);
			int now=0;
			for(int i=19;~i;i--)
			{
				if((x>>i&1)&&le[now|1<<i])
				  now|=1<<i;
			}
			printf("%d ",now&x);
			now=0;
			for(int i=19;~i;i--)
			{
				if(!(x&(1<<i))&&le[now|1<<i])
				  now|=1<<i;
			}
			printf("%d\n",now|x);
		}
	}
	return 0;
}

 

posted @ 2018-08-21 17:11  Ishtar~  阅读(271)  评论(0编辑  收藏  举报