本文将解答:
1.01字典树是什么
2.01字典树用途
3.01字典树实现代码
4.每个数按位插入顺序
一、01字典树是什么
首先我们都知道普通字典树(不懂?忘记了?点这里 ),字典树是插入字符串,利用字符串的公共前缀来减少查找时间,01字典树其实就是按位插入数字(将数字转化为2进制串),所谓“按位”即将该数字按2进制拆分为每一位是0或1的数字。
二、01字典树的用处
01字典树一般都是用于求异或最值问题。
三、01字典树的实现
和普通字典树并没有区别,只是插入的是二进制01串,只需要两个数组就行,一个是tree[][]数组,一个是val[]数组,val数组是建树插入x时记录该条树枝的最后一个节点为x ,记录后就可直接取出,不用再进行转化,方便异或。
代码实现放在最后
四、每个数按位插入顺序为什么是从高位开始的
01字典树树要求的是异或最值,我们都知道要利用字典树进行贪心策略。
怎么贪心?
首先我们知道异或是:两个位上的数字相同置0,不同为1。
假如我们求的是最大异或值,我们在每一位的选取上当然是尽量要选与当前位不同的。
先不管选取先后顺序,假如我们现在选到第i位,我们不管选0还是1,都能使最终答案数变化 2i,2i 我们就称它为贡献值,i越大贡献值2i就越大,我们改变这个数的能力就越大,故我们从高位开始插入和查询。
c++代码实现(有详细注释)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define max_size 100005
int num=0;
int tree[32*max_size][2];
//int book[32*max_size];01字典树不需要,因为长度是一样的,即层数都相同
//int sum[32*max_size];这个是求前缀数的,由于插入的每个数的最后一层的层数相同 ,所以也不用
int val[32*max_size];//01字典树特有 建树插入x时记录该条树枝的最后一个节点为x
void insert(int x)//建树
{
int now=0;
//为什么是逆序呢 ?
//要异或最大-贪心:要选不同的 (求异或值最小就选相同的), 因为第i位的贡献是2^i,
//要使异或值最大,i就要从高位开始选
for(int i=31;i>=0;i--){
int id=(x>>i)&1;
if(!tree[now][id])
tree[now][id]=++num;
now=tree[now][id];
//sum[now]++;
}
//book[now]=1;//标记是否为一个数的结束位
val[now]=x;//到这个节点为止才是一个数x
return;
}
int search(int x)
{
int now=0;
for(int i=31;i>=0;i--){
int id=(x>>i)&1;
////贪心策略:要求异或最大值,要优先选择和当前位不同的数
if(tree[now][id^1])//与当前位不同的数存在
now=tree[now][id^1];
else//不存在
now=tree[now][id];
}
return val[now];
}
int main(void)
{
int t,n,m,x;
scanf("%d",&t);
int i=0;
while(t--)
{
scanf("%d %d",&n,&m);
while(n--)
{
scanf("%d",&x);
insert(x);
}
scanf("%d",&x);m--;
printf("Case #%d:\n",++i);
printf("%d\n",search(x));
while(m--)
{
scanf("%d",&x);
printf("%d\n",search(x));
}
//重置字典树
memset(tree,0,sizeof(tree));
num=0;
}
return 0;
}