可持续化Trie_区间异或最大_bzoj3261

【bzoj3261】最大异或和

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 最大,输出最大是多少。

Input

第一行包含两个整数 N  ,M,含义如问题描述所示。
第二行包含 N个非负整数,表示初始的序列 A 。
接下来 M行,每行描述一个操作,格式如题面所述。

Output

假设询问操作有 T个,则输出应该有 T行,每行一个整数表示询问的答案。

Sample Input

5 5
2 6 4 3 6
A 1
Q 3 5 4
A 4
Q 5 7 0
Q 3 6 6
对于测试点 1-2,N,M<=5 。

对于测试点 3-7,N,M<=80000 。
对于测试点 8-10,N,M<=300000 。

其中测试点 1, 3, 5, 7, 9保证没有修改操作。
对于 100% 的数据, 0<=a[i]<=10^7。

Sample Output

4
5
6

HINT

对于      100%  的数据,     0<=a[i]<=10^7  

对于求区间异或的最大值这种问题,我们首先可以这样思考,我们要知道这个区间当中哪一段开始是最大的,那么我们需要维护一下他的前缀异或和。比如题目中给定的a[1]...a[n]我们用b[i]表示a[1]^...a^[i] 那么我们用b[i-1]^b[n]就可以得到a[i]^...^a[n]的值了, 然后再异或上题目中给定的x。到这里我们分析可以得到我们需要计算的其实是b[i-1]^b[n]^x的最大值,因为b[n]^x的值是确定的,那么我们只需要计算b[i-1]中i-1取哪一个的时候可以与b[n]^x异或的值最大即可。而我们又知道i是从题目中给定的l到r的范围内确定的,因此i-1属于[l-1,r-1],这样我们就确定好了区间的范围了。然后我们怎么维护每一个b[i]呢? 可以考虑可持久化的数据结构,这里用trie树因为存二进制位,讲到可持久化数据结构,应该有学过主席树,没有的话可以看我的上一篇,可持久化的思想就是保存每一个修改/更新的版本,只在每一个版本上进行需要更新的节点。 好了,看代码,我用的是非指针的,并且不是递归,感觉这样快点,虽然代码变长了。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
int a[maxn],b[maxn];
struct Node{
	int sum;
	int l;
	int r;
	Node(){
		l=0,r=0,sum=0;
	}
}node[maxn<<5];
int root[maxn],cnt,K=5,n,m;//K表示該題的数据范围的对应二进制的位数 
char ch[10];
int update(int last,int val){
	int now = ++cnt;//cnt表示的是该节点的标号  
	node[now] = node[last];//一开始的的时候新版本的根节点指向上一个版本的根节点
	int temp = now;
	for(int i=K;i>=0;i--){
		int bit = (val>>i)&1;
		if(bit){
			node[temp].l = node[last].l;//左边的不去修改,一直复制上一个版本的信息即可
			node[temp].r = ++cnt;//当前版本中该位置需要有更新所以新建节点 
			temp = node[temp].r, last = node[last].r; 
			node[temp].sum = node[last].sum+1;//不能是自己++ 需要保持与上一个版本的信息一致 所以在上一个版本基础上++ 
		}
		else{//该位是0 往左走  
			node[temp].r = node[last].r;
			node[temp].l = ++cnt;
			temp = node[temp].l, last = node[last].l;
			node[temp].sum = node[last].sum+1; 
		}
	}
	return now;//返回当前版本的根节点的标号 
}
int query(int pre, int now,int val){
	int ans = 0;
	for(int i=K;i>=0;i--){
		int bit = (val>>i)&1;
		if(bit){//当前位置是1 则需要异或0 可以得到max 所以往左走 
			if(node[now].l==0){//左节点没有 往又走 
				now = node[now].r;
				pre = node[pre].r;
				continue;
			}
			int count = node[node[now].l].sum - node[node[pre].l].sum;//当前版本与之前版本的个数差 
			if(count>0){
				ans += (1<<i);
				now = node[now].l;
				pre = node[pre].l;
			}
			else{
				now = node[now].r;
				pre = node[pre].r;
			}
		}
		else{//当前位是0 需要异或1得到最大  
			if(node[now].r==0){//右节点没有 往左走 
				now = node[now].l;
				pre = node[pre].l;
				continue;
			}
			int count = node[node[now].r].sum - node[node[pre].r].sum;//当前版本与之前版本的个数差 
			if(count>0){
				ans += (1<<i);
				now = node[now].r;
				pre = node[pre].r;
			}
			else{
				now = node[now].l;
				pre = node[pre].l;
			}
		}
	}
	return ans;
}
void init(){
	cnt = 0;//每一次都需要重置0的个数 
	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	memset(root,0,sizeof(root));
}
int main(){
	while(~scanf("%d%d",&n,&m)){
//		n++;
//		root[1] = update(root[0],0);//一开始要插入为0  因为最初异或和是0  
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			b[i] = b[i-1]^a[i];
			root[i] = update(root[i-1],b[i]); 
//			printf("%d\n",root[i]);
		}
		for(int i=1;i<=m;i++){
			scanf("%s",ch);
			if(ch[0] == 'A'){
				n+=1;
				scanf("%d",&a[n]);
				b[n] = b[n-1]^a[n];
				root[n]  = update(root[n-1],b[n]);
			}
			else{
				int l,r,x;
				scanf("%d%d%d",&l,&r,&x);//因为题目中需要求a[i]^..a[n]^x 最大
				//我们将b[n]作为记录a[1]^..a[n]的前缀和 所以要求该式子 我们只需要求 b[i-1]^b[n]^x 最大即可
				//所以我们要定位i-1 且 l<=i<=r 所以 l-1<=i-1<=r-1 因此在下面的区间搜寻即可 
				int ans = query(root[l-2],root[r-1],x^b[n]);
				printf("%d\n",ans);
			}
		}
	}
} 


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值