题解 CF706D 【Vasiliy's Multiset】

题目传送门

算法分析:Trie 树

题目中唯一的运算是按位异或,我们可以考虑按位异或的本质。既然是一位一位异或,那么我们直接把 \(x\) 转化成一个二进制数不就好了吗?

在插入时,我们可以先将 \(x\) 转化为一个32位二进制数,然后从高位到低位插入 Trie 树,并对每个树上每个节点 \(p\) 记录一个 \(rec_p\) 表示这个节点被 \(rec_p\) 个数字使用过。

在查找时,优先沿着相反的边向下移动指针,并累加答案即可。

在删除时,对沿途节点的 \(rec_p\) 减1即可。


注意

  • 数字必须从高位到低位插入 Trie 树,否则会引起冲突。

  • 与一般 Trie 树不同的地方是,本题中不需要打结尾标记(显然每个数都是32位),但是需要额外记录每一个节点被几个数使用。

  • 数组记得开大一点,否则容易 RE。


code:

#include<bits/stdc++.h>
#define reg register
#define F(i,a,b) for(reg int i=(a);i<=(b);i++)
using namespace std;
inline char get_char(); 
const int N=3e6+10;
int T,n,cnt=1;
int c[40],trie[N][3];
int rec[N];
inline void change(long long x) {
	memset(c,0,sizeof(c));
	int ct=0;
	while(x)c[++ct]=x&1,x>>=1;
}
inline int reply(){
	int p=1,s=0;
	for(reg int i=33;i>=1;i--){
		int &k=c[i];
		s<<=1;
		if(!trie[p][k^1] or !rec[trie[p][k^1]])p=trie[p][k];
		//相反的边走不通 
		else p=trie[p][k^1],s|=1;//走相反的边 
	}
	return s;
}
inline void insert(){//插入 
	int p=1;
	for(reg int i=33;i>=1;i--){
		int &k=c[i];
		if(!trie[p][k])trie[p][k]=++cnt;
		p=trie[p][k],rec[p]++;//记录该节点被几个数用过 
	}
}
inline void _delete(){//删除 
	int p=1;
	for(reg int i=33;i>=1;i--){
		int &k=c[i];
		p=trie[p][k];
		rec[p]--;//减去这个数的贡献 
	}
}
int main() {
	scanf("%d",&n);
	insert();//插入开始时的0 
	while(n--){
		static char c;
		static int x;
		c=get_char();
		scanf("%d",&x);
		change(x);//将x变成二进制数 
		if(c=='?')printf("%d\n",reply());
		else if(c=='+')insert();
		else _delete();
	}
	return 0;
}
inline char get_char(){//读入字符 
	reg char c=getchar();
	while(c!='+' and c!='-' and c!='?')c=getchar();
	return c;
} 

AC

欢迎交流讨论,请点个赞哦~

posted @ 2021-06-23 16:00  Maplisky  阅读(40)  评论(0编辑  收藏  举报