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;
}
posted @ 2024-01-16 22:13  fengxue-K  阅读(33)  评论(0)    收藏  举报