您的位置:首页 > 其它

01字典树小结-2017HZAU现场赛H-MathematicalGame

2017-05-01 18:31 375 查看

01字典树

想了解01字典树先要知道字典树(Trie树):http://blog.csdn.net/williamsun0122/article/details/71056547

其实01字典树就可以看成是把一个数的二进制字符化后插入到一颗一般的字典树中。

一般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]

所以对于区间异或和之类的题目,比如求区间异或和的最大值,我们可以将r前的所有前缀异或和加入到一个01字典树中,然后查询一下[1,r]就可以得到以r为右边界的最大异或和区间。

在查询最大异或值时我们用贪心的策略,比如我们在字典树中查询10101的最大异或值。

我们从最高位即第5位开始查(我省略掉前面的0位),由于第5位是1(对于其它的任意数,我们设为idx),之后看字典树中有没有第5为是1^1(idx^1)的数,如果有就进入0(idx^1)的节点(贪心思想,即首先保证该位异或后值为1,使异或值尽可能大),没有就进入1(idx)节点,然后从高位到低位依次这样即可。

说了那么多,我们看一道例题代码感受一下就知道了。

2017HZAU现场赛H-MathematicalGame

题目链接:http://acm.hzau.edu.cn/problem.php?id=1206

题意:有T组样例,每组样例给n个数,a1…an(n<=1000000)。求这n个数中最大异或和值的区间。有多个答案区间按字典序输出。

题解:把1-n的所有前缀异或和插入01字典树,然后按我上面说的区间异或的性质扫一遍就可以了。这题就还要注意一下区间按字典序输出,之前没看到这个,一直WA。具体看代码注释。

代码:

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e6+5;  //数的最大个数
const int INF = 0x3f3f3f3f;
int ch[maxn*32][2];  //int型是32位,所以32*maxn
int idx[maxn*32],cnt;
int ans,l,r;  //ans保存最大异或和的值,l,r为其区间

void init()  //01字典树的初始化
{
memset(ch,0,sizeof(ch));
memset(idx,INF,sizeof(idx));
cnt=1;
ans=0;
l=r=0;
}

void trie_insert(int id,int x)  //插入序号为id的前缀异或和x
{
int num=0;
for(int i=31;i>=0;i--)
{
int tmp = (x>>i)&1;  //从第32位开始,以下和字典树类似
if(!ch[num][tmp]) ch[num][tmp] = cnt++;
num = ch[num][tmp];
}
//保证同一异或和的序号最小,为之后字典树输出区间准备
//比如插入1,5后再插入3,5最后idx[num]=1
if(id<idx[num]) idx[num] = id;
}

void trie_query(int id,int x)
{
int num=0,m=0;
//m是以id为右边界,数x的异或的最大值
//num是与x异或后为m的值的节点序号
for(int i=31;i>=0;i--)
{
int tmp = (x>>i)&1;
if(ch[num][tmp^1])
{
num = ch[num][tmp^1];
m += (1<<i);
}
else
{
num = ch[num][tmp];
m += (0<<i);
}
}
if(m>ans)  //如果m大于当前最大异或和的值,直接更新区间和最大异或和值
{
l = idx[num];
r = id;
ans = m;
}
if(m==ans)  //如果等于,区间按字典序更新
{
if(idx[num]<l)
{
l = idx[num];
r = id;
}
if(idx[num]==l)
{
if(id<r)
{
l = idx[num];
r = id;
}
}
}
}

int main()
{
//freopen("std.in","r",stdin);
//freopen("out.txt","w",stdout);
int T,n,num,tmp;
scanf("%d",&T);
for(int ca=1;ca<=T;ca++)
{
init();
trie_insert(0,0);
num=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&tmp);
{
num ^= tmp;  //num为i为前缀的异或和
trie_insert(i,num);
trie_query(i,num);  //查询以i为右边界的最大异或和值的区间
}
}
printf("Case #%d:\n%d %d\n",ca,l+1,r);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: