CF-817-E-Trie
817-E 题目大意
给定一个初始为空的可重集\(S\)。现有\(Q\)次操作,操作的类型有三种:
\(1,x:\)向集合\(S\)中加入一个\(x\)。
\(2,x:\)从集合\(S\)中删除一个\(x\),数据保证\(x\)存在。
\(3,x,l:\)询问集合\(S\)中有多少个数异或上\(x\)的结果小于\(l\)
Solution
要求一个集合可插入,可删除,且询问异或相关的结果,第一想法肯定是想到\(01Trie\)
操作一和操作二很容易实现,剩下的就是需要考虑操作三如何统计:
在\(Trie\)中查询集合中元素和\(x\)的异或值,当前枚举到第\(i\)位,在\(Trie\)中走到了节点\(p\),分两类情况:
1、如果\(l\)的第\(i\)位为\(1\),则\(p\)所在子树中的元素第\(i\)位与\(x\)相同的元素与\(x\)异或的结果一定小于\(l\),我们直接加上这一部分的元素数量,然后\(p\)走到与\(x\)的第\(i\)位不同的子树中去,寻找其他的可能满足条件的元素。
2、如果\(l\)的第\(i\)位为\(0\),则\(p\)所在子树中的元素第\(i\)位与\(x\)不同的元素与\(x\)异或的结果一定不小于\(l\),此时\(p\)应当走到与\(x\)的第\(i\)位相同的子树中去查找元素。
时间复杂度\(O(nlogn)\)。
- 要注意的是,如果\(p\)走到了\(0\)号点,说明不存在合法的元素了,直接\(return\)即可。
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e5+10;
int ch[N*31][2],cnt[N*31];
int idx=0;
void update(int x,int k){
int p=0;
for(int i=30;~i;i--){
int j=(x>>i)&1;
if(!ch[p][j]) ch[p][j]=++idx;
p=ch[p][j];
cnt[p]+=k;
}
}
int query(int x,int l){
int res=0;
int p=0;
for(int i=30;~i;i--){
int j=(x>>i)&1;
int k=(l>>i)&1;
if(!k){
p=ch[p][j];
}else{
res+=cnt[ch[p][j]];
p=ch[p][!j];
}
if(!p) break;
}
return res;
}
void solve(){
int q;
cin>>q;
while(q--){
int op,x,l;
cin>>op>>x;
if(op==1){
update(x,1);
}else if(op==2){
update(x,-1);
}else{
cin>>l;
cout<<query(x,l)<<'\n';
}
}
}
int main(){
ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T=1;
//cin>>T;
while(T--){
solve();
}
return 0;
}