Codeforces Round #367 (Div. 2) D. Vasiliy's Multiset(01字典树+贪心)

题目

q<=2e5个操作

操作有三种

+ x 代表把x加入multiset A

- x 代表把x从multiset A里删去一个

? x 询问multiset里的一个值y,使得ans=y^x最大,输出ans

注意0恒在multiset中,无需加入

思路来源

https://www.cnblogs.com/dwtfukgv/p/5771127.html

航神

题解

这个东西,可能是叫可持久化字典树,毕竟支持删除

然而也很简单啦,如果(x>>i)&1这一位能取它的异或值就取它的异或值

如果能异或,这一位的答案肯定是有1的,

否则就不能异或,这一位答案就没有1,

开始被自己先搞了一发古怪的贪心搞过去了

然后网上看代码,比自己的将近短一半,

贪心思路也简单不少,学一学写法

代码1(借鉴)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=6e6+10;
//01字典树 
int ch[maxn][2],num[maxn],tot;
int q,x;
char op[3];
void insert(int x,int op)//op=1加 op=1减 
{
	int rt=1;
	for(int i=30;i>=0;--i)
	{
		int j=(x>>i&1)>0;//x在i位是否为1  
		if(!ch[rt][j])ch[rt][j]=++tot;
		rt=ch[rt][j];
		num[rt]+=op;
	} 
}
int query(int x)
{
	int rt=1,ans=0;
	for(int i=30;i>=0;--i)
	{
		int j=(x>>i&1)^1;//^1与==0等价 
		if(num[ch[rt][j]])//如果可异或,说明最后结果有这一位 
		{
			ans|=(1<<i);
			rt=ch[rt][j];
		}
		else rt=ch[rt][1-j];//不可异或,结果没有这一位 
	}
	return ans;
}
void init()
{
	memset(num,0,sizeof(num));
	memset(ch,0,sizeof(ch));
	tot=1;//root从1开始 
}
int main()
{
	while(scanf("%d",&q)==1)
	{
		init();
		insert(0,1);//把0加进字典树 
		while(q--)
		{
			scanf("%s%d",op,&x);
			if(op[0]=='+')insert(x,1);
			else if(op[0]=='-')insert(x,-1);
			else printf("%d\n",query(x));
		} 
	}
	return 0;
}

代码2(自己乱搞的)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=6e6+2e5;
struct node
{
  int num,Next[2];
}trie[maxn];
int tot,root;
int n,m;
void insert(int r,int s[],int len,int op)
{
   for(int i=0;i<len;i++)
   {
   	  //printf("%d",s[i]);
   	  trie[r].num+=op;
      if(trie[r].Next[s[i]]==0)trie[r].Next[s[i]]=++tot;
      r=trie[r].Next[s[i]];
   }     
   trie[r].num+=op;
}
int query(int r,int x,int s[],int len)
{
     int now=31,res=0;
     //puts(":");
	 //for(int i=0;i<len;++i)
     //printf("%d",s[i]);
     bool flag=0; 
     for(int i=0;i<len;i++)
     {
         if(s[i]==0&&trie[trie[r].Next[1]].num>0)//使答案变大,01
         {
        	if(!flag)flag=1;
        	res+=(1<<now);
        	r=trie[r].Next[1];
         }
         else if(trie[trie[r].Next[0]].num>0)//不变但没变坏,10或00 
		 {
		 	r=trie[r].Next[0];
		 } 
		 else if(flag)//为了高位而牺牲低位,走11 
         {
        	res+=(1<<now);
        	r=trie[r].Next[1];
         }
         else break;//第一位就要牺牲x 不值得
         now--;
     }
     return res^x;
}
void init()
{
    trie[0].num=tot=0;
	root=++tot;
	memset(trie,0,sizeof(trie));
}
char op[5];
int ans[35];
int main()
{
    while(~scanf("%d",&n))
    {
     init();
     for(int i=0;i<n;i++)
     {
      int x;
      scanf("%s%d",op,&x);
      int now=0;
	  for(int i=31;i>=0;--i)ans[now++]=(x>>i&1);
	  if(op[0]=='?')printf("%d\n",query(root,x,ans,now));
	  else if(op[0]=='+')insert(root,ans,now,1);
	  else if(op[0]=='-')insert(root,ans,now,-1);
     }
    }
    return 0;
}

拓展

询问区间最大异或和。

把前缀异或和的值像这样插到01字典树里即可

即边处理前缀异或和边插入边处理询问。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值