01字典树(OR问题)

01字典树

用途:解决区间异或和之类的问题

异或的性质:

1. 交换律

2. 结合律,即(a^b)^c = a^(b^c))

3. 自反性,即x^x=0

4. x^0=x

有上述性质,对于区间异或和要知道此性质XOR[l,r] = XOR[1,l-1] ^ XOR[1,r]

注意:INT类型1<<31会溢出

区间异或和的题目:比如给一组数列求l,r,要求[l,r]是所有区间中异或和最大

由X xor X = 0 ; 0 xor Y = Y;所有【l,r】=【1,r】XOR【1,l - 1】。这样在一颗加入了r 前的所有前缀异或和的01字典树上查找【1,r】就能得到以r为右边界的最大异或和。简单来说,就是先把数列的每个数变成前缀异或和,这样求区间中异或和最大就变成了找两个数异或和最大(因为【1,r】得到以r为右边界的异或和,【1,l-1】就能得到以l-1为右边界的异或和,易得【l,r】=【1,r】XOR【1,l - 1】的区间异或和值)。每个数字先贪心搜索,再插入,就能实现其跟前面出现过的都进行比较了,如全部插入再全部搜,容易出现重复,时间会翻倍。

poj3764

要求在树中找两个结点,且两个结点之间路径唯一,求最长的异或路径。很明显不能用暴力,O(N2)时间复杂度100000个点。首先我们需要知道一个性质:a^b = (a^c)^(b^c),这样就可以考虑找出a与b公共的c,实际上就是求出从根节点到每个节点的异或值,这样任意两个点做异或,即是他们之间的异或路径(相同部分异或抵消了)。先遍历DFS遍历一遍,找出所有结点到树根的路径异或值,则问题就转化成了求这些点中任意两个点的异或值,接下来就很简单了,将DFS所得的每个点的异或值转化成二进制数存进字典树,然后从高位至低位对字典树进行贪心,找出最大的那个值。这里跟上一题一样,每个数字先贪心搜索,再插入,就能实现其跟前面出现过的都进行比较了,如全部插入再全部搜,容易出现重复,时间会翻倍。字典树的时间复杂度为O(31*n),dfs时间复杂度为O(n)。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 100000 + 5;         //集合中的数字个数
int ch[32 * maxn][2];               //节点的边信息
LL value[32 * maxn];               //节点存储的值
int node_cnt;                     //树中当前节点个数
inline void init(){              //树清空
    node_cnt = 1;            //树中当前节点个数,实即也是下一个插入结点编号!技术!
    memset(ch[0],0,sizeof(ch));
}
inline void Insert(LL x){        //在字典树中插入 X
                          //和一般字典树的操作相同 将X的二进制插入到字典树中
    int cur = 0;             //初始化当前点编号
    for(int i = 32;i >= 0;--i){    //模拟偏移位数
        int idx = (x >> i) & 1;  //依次读出打入数字的每个数位0或1
        if(!ch[cur][idx]){     //如果当前结点的IDX数组为空
            memset(ch[node_cnt],0,sizeof(ch[node_cnt]));//刷新待更新结点射出的边
//因可能上一样例有边
            ch[cur][idx] = node_cnt;//CUR结点射出的IDX边指向新结点node_cnt
            value[node_cnt++] = 0;  //赋值后继续开辟新结点,即+1
        }						//上面设VAL为0,因多组询问,可能上一样例是叶子
        cur = ch[cur][idx];//递归下一层,每层一个位,32位即32层,每个结点有01两儿
    }
    value[cur] = x;//最后的节点插入value,即叶子存值,每次询问时贪心搜到叶子(见下文)
}
inline LL Query(LL x){        //在字典树中查找和X异或的最大值的元素Y 返回Y的值
    int cur = 0;            //初始化当前点编号
    for(int i = 32;i >= 0;--i){  //模拟偏移位数
        int idx = (x >> i) & 1; //依次读出打入数字的每个数位0或1
        if(ch[cur][idx ^ 1]) cur = ch[cur][idx ^ 1];//贪心地ch[cur][idx ^ 1]走,如果存在的话
        else cur = ch[cur][idx]; //没有的话只能往ch[cur][idx]走
    }
    return value[cur];        //返回要异或的叶子编号
}
int main(){
    Insert(1);
    Insert(2);
    Insert(3);
    Insert(4);
    Insert(5);
    cout<<Query(3)<<endl;// 查找和X异或最大的元素,此处询问的是3
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值