CodeForces 824D Round#430 Div2D:Vitya and Strange Lesson :01字典树

题意:给出n(<=3e5)个数字(<=3e5),现在对原序列的每个数字连续做m(<=3e5)次异或操作。每次把所有数字都抑或一个x。(第k次抑或是在第k-1次抑或的结果上进行)。每次操作之后求mex。mex定义为:序列中最小的 没有出现过的非负整数。比如mex[0,1,2,4,5]=3。mex[2,4,6,9]=0。

题解:首先,第一次要求把所有数字抑或x1,第二次要求吧所有数字继续抑或x2,就等价于把原数列抑或一个x1^x2,于是原数列不需要做改变,每次做查询就可以了。

考虑mex的二进制位,mex是*最小*的*抑或之后*没出现过的数字(从高位开始决定,如果高位可以为0就确定为0,否则确定为1,那么我们吧mex还原回去就是mex^x,mex^x这个数字原序列中不存在。那么也就是mex^x从高位开始决定,如果高位可以和x这一位相同,就确定为x的那一位,否则选不同于x那一位的数字)。把所有数字只考虑20位。mex最高位(第19位)选0不合法意味着:抑或完成之后,新数列出现了[0..2^19-1]范围内的所有数字。而这些数字共同的特点是 原数第19位和x相同(这样抑或之后才为0嘛)。而后边的[0..18]这19位中,新数可以实现任意的组合,那么原数必然也实现了所有的组合。如果我们把所有数字放到一个类似Trie的树上(字符集={0,1}),高位浅,低位深这样建,那么mex最高位不能是0,意味着从根出发,走(x的第19位)这个字符 达到的那个节点是一个满的树。这样如果我们让最高位=0了,我们继续往下走完所有20层,是无法得到一个“没有出现过的整数”的(最高位=0的所有数字都会出现在新数中)。因此只有在这种情况下,mex最高位被迫选1,然后我们继续同样的考虑第18位怎么选。

把思路总结以下就是:先建一个满的,总共有20层的二叉树,每个节点记一个full标记。对原数列所有数字进行二进制分解,插入到树中,把结束位置的full标记改成true。然后再build以下这个树,就是把非叶子结点的full标记更新一下。(如果两个孩子都是full,那么把我也改成full)然后得到一个询问X(=输入数据中 xi 的抑或前缀和)然后从最高位开始选,优先选和x相同的,如果选x相同的,到达的那个点是个满树,那就只能选和x不同的,同时确定了答案(mex)的这一位是1。进行20位的选择之后得到mex输出即可。


总的来说,代码很简单,但是写之前要想清楚才行。


依然采用结构体指针形式的Trie树。

Code:

#include<bits/stdc++.h>
using namespace std;
const int MAX = 3e5+100;
struct Node{
    Node* nxt[2];
    int deep;
    bool full;
};
Node* create(int dep){
    Node* n = (Node*)(malloc(sizeof (Node)));
    memset(n->nxt,0,sizeof(n->nxt));
    n->deep = dep;
    n->full = false;
    return n;
}
Node* root = create(-1);
void init(Node* nod){
    if (nod->deep==19){
        return;
    }
    nod->nxt[0] = create(nod->deep+1);
    init(nod->nxt[0]);
    nod->nxt[1] = create(nod->deep+1);
    init(nod->nxt[1]);
}
void insert(int x){
    Node* nod = root;
    for (int i=19;i>=0;i--){
        int id = (x&(1<<i))?1:0;
        nod = nod->nxt[id];
    }
    nod->full = true;
}
void build(Node* nod){
    if (nod->deep==19){
        return;
    }
    build(nod->nxt[0]);
    build(nod->nxt[1]);
    nod->full = nod->nxt[0]->full&nod->nxt[1]->full;
}
int query(int x){
    Node* nod = root;
    int res =0;
    for (int i=19;i>=0;i--){
        int id = (x&(1<<i))?1:0;
        if (nod->nxt[id]->full){
            nod = nod->nxt[!id];
            res+=(1<<i);
        }else{
            nod=nod->nxt[id];
        }
    }
    
    return res;
}
int m,n;
int main(){
    scanf("%d%d",&n,&m);
    init(root);
    for (int i=0;i<n;i++){
        int temp;
        scanf("%d",&temp);
        insert(temp);
    }
    build(root);
//    cout<<"YES"<<endl;
    int K =0;
    while (m--){
        int temp;
        scanf("%d",&temp);
        K^=temp;
        printf("%d\n",query(K));
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值