这里戳题目
题意:首先给你一个集合,这个集合里面一开始只有零(一开始是有零的,别用个空的集合,并且这个集合允许有重复的值),然后有三种操作,如果输入时加号,就往这个集合新增一个数字,如果是减号,就把减号后的那个数字从集合里面去掉,如果是问号,就从集合里面找一个数,这个数字和问号后面的数字异或的结果是最大的。
异或:意思是说有两个数字,如果不是二进制,就转化成二进制,然后两两比较,相同为零,不同为一,再转化成自己想要的进制就行了,在代码中,a和b异或就是a^b
思路:其实如果只是增删,直接就是一个map或者什么之类的就行了,然而这个要搞异或,如果直接查找肯定超时,所以这里要用到字典树,可以说是一个字典树的模板题。首先是将要增加进去的放到字典树里面去,然后是删除,再来是异或。其实难点在异或,异或的时候,如果你想要得到异或的最大值,那么就要不断地找不同的,所以在建立数组的时候,我是开了个二维的,只有两列,一个是0,一个是1,因为是二进制嘛,所以用这个来查找不一样的。
字典树:其实以前也遇到过关于异或的题目,然而因为看不懂所以没看,现在遇到了,就顺便了解了下字典树。字典树,如字面意思,根据前面已有的字母或数字来查找你想要的,也时根据已有的字母或数字来增加你想要的数据。原理就是相同的前缀。而且他们的每个节点都有一个特定的编号,如此便不会搞混,就像是有一个父节点,然后有多条链延伸出来一样。所以如果你要像这道题一样删除数据,就还要新建一个数组来记录这个节点用了多少次。一开始我是一脸懵逼的,但是做完这道题,感触良多啊。
#include<stdio.h> #include<iostream> #include<string.h> #include<math.h> #include<algorithm> #include<vector> #include<string> #include<queue> #include<map> #include<stack> #include<set> #define ll long long #define maxn 100010 #define PI acos(-1.0) //圆周率 const ll INF = 1e9; const int len = 31; using namespace std; struct node { int sz; int ch[maxn*45][2]; int sum[maxn*45]; void trie() { sz=1; memset(ch,0,sizeof(ch)); memset(sum,0,sizeof(sum)); } void add(int x) { int u=0; bool f; for(int i=len;i>=0;i--) { f=x&(1<<i); if(ch[u][f]==0) ch[u][f]=sz++; u=ch[u][f]; sum[u]++; //每个节点都是唯一的,所以不需要开二维数组 } } void del(int x) { int u=0; bool f; for(int i=len;i>=0;i--) { f=x&(1<<i); u=ch[u][f]; sum[u]--; } } int query(int x) { int u=0; int v=0; bool f; for(int i=len;i>=0;i--) { f=x&(1<<i); if(sum[ch[u][!f]]) f=!f; //尽量找二进制里面和X不一样的,异或的结果才能尽量的大 v=v|(f<<i); u=ch[u][f]; } return v; } }st; int n; int main() { scanf("%d",&n); st.trie(); st.add(0); //注意这道题一开始就有一个零在里面 char a[2]; int x; for(int i=0;i<n;i++) { scanf("%s",a); scanf("%d",&x); if(a[0]=='+') st.add(x); else if(a[0]=='-') st.del(x); else if(a[0]=='?') { int ans=x^st.query(x); printf("%d\n",ans); } } return 0; }