转自http://blog.csdn.net/BerryKanry/article/details/76165196
世界真的很大
trie树贪心求最大异或和大概也就是那么回事了
但是对于区间的查询就不是那么容易的了
考虑主席树的思想,怎么得到区间的值域的
这就是可持久化的trie树
说来容易
指针教做人哪
看题先:
description:
给定一个非负整数序列 {a},初始长度为 N。
有 M个操作,有以下两种操作类型:
1 、A x:添加操作,表示在序列末尾添加一个数 x,序列的长度 N+1。
2 、Q l r x:询问操作,你需要找到一个位置 p,满足 l<=p<=r,使得:
a[p] xor a[p+1] xor ... xor a[N] xor x 最大,输出最大是多少。
1
2
3
4
5
6
7
input:
第一行包含两个整数 N ,M,含义如问题描述所示。
第二行包含 N个非负整数,表示初始的序列 A 。
接下来 M行,每行描述一个操作,格式如题面所述。
1
2
3
4
output:
假设询问操作有 T个,则输出应该有 T行,每行一个整数表示询问的答案。
1
首先考虑怎么把连续的异或和转化掉
考虑连续一段的数字之和的转化,除了线段树之外的高级东西,前(后)缀和这个东西在这种不修改的情况下就要优秀的多了
[L,R]的数字之和可以转化成sum[R]-sum[L-1]或last[L]-last[R+1],而考虑异或和这种东西,如果一个数连续异或偶数次就是0,而任何数异或0都是本身,除了0自己
所以如果处理一段连续的异或和的话,就可以考虑用异或前(后)缀和来处理了,而且由于有添加的操作,只会影响后缀而不会影响前缀,如果使用后缀的话每次修改都需要把前面的后缀全部修改一遍,这是不能接受的
既然能达到一样的区间处理效果,尽管每次求的是最大的后缀,但我们仍然选择用前缀维护
每次就用X^=sum[n],再用这样的X在L到R范围内找一个sum使其异或和最大
这就转化成了在一堆数里面选一个数与指定数异或和最大的经典trie树贪心的问题了
但我们不可能每次询问都对指定区间建一颗trie树,这样询问的代价就是O(n)的了,这是不能接受的。我们希望能够预处理出每次询问的trie树,而询问优势在线的,
可持久化trie树就应运而生了
考虑可持久化线段树,主席树的实现方式,将R的trie树减去L-1的trie树,类比主席树,得到的就应该是L到R区间的所有数得到的trie树,而这样的一颗颗trie树是可以预处理的,而每次在数列末尾加数字时再新建一颗trie树,就可以动态的维护这样的一颗颗树了
大概思想是有了,接下来考虑实现方法
还是要类比主席树
每一个点都建一颗trie树,存的是1到这个点的所有数的二进制的01串,而对于每一个点都重新建一颗这样的树,时间是O(n^2)的,不能接受的,而类比主席树,每一个点对应的树和前一颗树不同的地方只有一个数而已,完全没有必要重新建一棵树,这样空间时间上都能省一笔
可持久化trie树的大概思想好像差不多了
那么我们现在需要考虑的就只有具体的建树思路了
对于相邻的两棵树,只有一个数不同而已,一个数在trie上对应的就是一条链,建树时我们沿着这条链走,把这条链构造进新树里,其余结构全部指向旧树
就是说新树只有这一条链的部分是新建的,而其他部分全部与旧树共享,类比主席树
还剩下查询操作,关键是判断在当前区间内有没有trie树的这条链,考虑主席树是怎么做的,对每一个节点开一个域cnt,记录这个节点以下有几个数,每次按trie贪心的方法查询时,判断R树的节点cnt值减去L-1树的节点的cnt值为不为0,不为0就说明区间内有这么一个数可以走
类比主席树
大概想一下,具体化一下具体的代码就可以开始写的,还是挺好写的,只能说指针教做人哪
学新东西时还是注意一下细节
首先是指针写法,防RE的办法就不说了,很简单,注意trie树insert时使用的是递归还是while,是有区别的,while需要另设一个指针,不能直接用root跳
还有就是边界问题,一定要在trie树里一开始插入一个0,这很重要,因为如果取1到n为值的话,应该是sum[n]^sum[0],如果trie树里没有0的话,就无法实现这一步操作,我的做法是直接多建一棵树,往里面插值0,然后将n棵树依次往后推一位,这样第一棵树的值就是0了,每次查的时候就查L-1到R就行了,不然应该是L-2到R-1才对(这个也很重要,因为L到R选择一个后缀,是指用1到X的异或和异或上sum[L-1]到sum[R-1],而想要得到包含sum[L-1]和sum[R-1],不包含sum[R] 的trie树,就要用第R-1颗树减去L-2颗树,而我们在之前多建了一个点,所以查R到L-1就行了)
完整代码:
#include<stdio.h>
#include<cstdio>
#include<algorithm>
using namespace std;
struct node
{
int cnt;
node *ch[2];
}pool[20000010],*tail=pool,*root[600010],*null;
int n,m,sum[600010];
char ss[10];
node *newnode()
{
node *nd=++tail;
nd->cnt=0;
nd->ch[0]=nd->ch[1]=null;
return nd;
}
void init()
{
null=++tail;
null->cnt=0;
null->ch[0]=null->ch[1]=null;
}
void insert(node *ne,node *&nd,int x)
{
nd=newnode();
node *now=nd;
for(int i=24;i>=0;i--)
{
now->ch[0]=ne->ch[0],now->ch[1]=ne->ch[1];
now->cnt=ne->cnt+1;
int idx=(x&(1<<i))>0;
now->ch[idx]=newnode();
now=now->ch[idx];ne=ne->ch[idx];
}
now->cnt=ne->cnt+1;
}
int query(node *ne,node *nd,int x)
{
int num=0;
for(int i=24;i>=0;i--)
{
int idx=(x&(1<<i))>0;
if(nd->ch[!idx]->cnt-ne->ch[!idx]->cnt)
{
num|=(1<<i);
ne=ne->ch[!idx],nd=nd->ch[!idx];
}
else
ne=ne->ch[idx],nd=nd->ch[idx];
}
return num;
}
int main()
{
init();
scanf("%d%d",&n,&m);
n++;
for(int i=2;i<=n;i++)
{
scanf("%d",&sum[i]);
sum[i]^=sum[i-1];
}
root[0]=newnode();
for(int i=1;i<=n;i++)
insert(root[i-1],root[i],sum[i]);
while(m--)
{
scanf("%s",ss);
if(ss[0]=='A')
{
scanf("%d",&sum[++n]);
sum[n]^=sum[n-1];
insert(root[n-1],root[n],sum[n]);
}
else
{
int L,R,X;
scanf("%d%d%d",&L,&R,&X);
X^=sum[n];
printf("%d\n",query(root[L-1],root[R],X));
}
}
return 0;
}
/*
Whoso pulleth out this sword from this stone and anvil is duly born King of all England
*/