初试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]

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

题意:求n个数中最大异或和值的区间。有多个答案区间按字典序输出。

题解:把1-n的所有前缀异或和插入01字典树,然后按区间异或的性质扫一遍就可以了。复杂度(n*32(字典树深度))

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<string>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
const int INF=0x3f3f3f3f;
int n,num,ans,l,r,cnt;
int ch[1000003*32][2];
int idx[1000003*32];//记录字典序
void init()
{
    num=0;cnt=1;ans=l=r=0;
    memset(idx,INF,sizeof(idx));
    memset(ch[0],0,sizeof(ch[0]));
}
void Insert(int id,int x)
{
    int k=0;
    for(int i=31;i>=0;i--)
    {
        int tmp=(x>>i)&1;
        if(!ch[k][tmp])
        {
            memset(ch[cnt],0,sizeof(ch[cnt]));ch[k][tmp]=cnt++;
        }
        k=ch[k][tmp];
    }
    if(id<idx[k])idx[k]=id;//保证同一个异或和的字典序最小。每一个k值代表一个异或和
}
void query(int id,int x)
{
    int k=0,res=0;
    for(int i=31;i>=0;i--)
    {
        int tmp=(x>>i)&1;//贪心策略,从高位开始,让高位尽可能的为1,那么这个数位如果是1,那么就找0(1^1),如果是0,那么就找1(0^1);
        if(ch[k][tmp^1]){k=ch[k][tmp^1];res+=(1<<i);}
        else{k=ch[k][tmp];res+=(0<<i);}//如果并没有,那么往下贪心,这一位不变
    }
    if(res>ans){ans=res;l=idx[k];r=id;}
    else if(res==ans)
    {
        if(idx[k]<l){l=idx[k];r=id;}
        else if(idx[k]==l&&id<r){r=id;}
    }
}
int main()
{
    int t,x;scanf("%d",&t);
    for(int ca=1;ca<=t;ca++)
    {
        scanf("%d",&n);printf("Case #%d:\n",ca);
        init();Insert(0,0);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&x);num^=x;
            Insert(i,num);query(i,num);
        }
        printf("%d %d\n",l+1,r);//因为XOR[l,r] = XOR[1,l-1] ^ XOR[1,r],所以l需要+1
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值