为了做13年南京网络赛的一道题 学了这个01字典树
看了别人的模板 之后切了几道水题
现在总结一下
01字典树的实现可以看成是把一个数的二进制字符化后插入到一颗一般的字典树中
比如在01字典树种插入3时 相当于在字典树中插入00 …..00011(一共33为,这个根据具体实现不同)
查找最大异或值的时候我们是从最高位 向下贪心查找 贪心策略为:当前查找第k位 二进制数位IDX 如果存在IDX ^ 1的节点 我们就进入这个节点 否则进入IDX节点
贪心策略的证明: 如果这时我们进入了第K位为IDX 的节点 那么 第k位为IDX ^ 1 的节点组成的数 异或X一定更大
最后关于01字典树的使用:
不用关心代码内部是如何实现的
只将01字典树看做是一个数集 我们可以在这个集合中查找和X异或最大的元素
应用:
经常有区间异或和的题目:比如求[l,r]的异或和
由X xor X = 0 ; 0 xor Y = Y;所有【l,r】 = 【1,r】 XOR 【1,l - 1】
这样在一颗加入了r 前的所有前缀异或和的01字典树上查找【1,r】就能得到以r为右边界的最大异或和
模板:
const int maxn = 100000 + 5; //集合中的数字个数
typedef long long LL;
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;
if(!ch[cur][idx]){
memset(ch[node_cnt],0,sizeof(ch[node_cnt]));
ch[cur][idx] = node_cnt;
value[node_cnt++] = 0;
}
cur = ch[cur][idx];
}
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;
if(ch[cur][idx ^ 1]) cur = ch[cur][idx ^ 1];
else cur = ch[cur][idx];
}
return value[cur];
}