一般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;
}