BZOJ 3261 最大异或和 可持久化01字典树

给出一个序列长度 n ≤ 3 e 5 n\leq3e5 n3e5的数组,支持两个操作,1.在数组的末尾插入一个数。2.给定区间 [ l , r ] [l,r] [l,r]和一个数 x x x,查询这个区间里面使得 ( ⊕ i = p n a i ) ⊕ x (\oplus_{i=p}^{n}{a_{i}})\oplus x (i=pnai)x最大的那个 p p p
事实上 ⊕ i = p n a i = ( ⊕ i = 1 p − 1 a i ) ⊕ ( ⊕ i = 1 n a i ) \oplus_{i=p}^{n}a_{i}=(\oplus_{i=1}^{p-1}a_{i})\oplus(\oplus_{i=1}^{n}a_{i}) i=pnai=(i=1p1ai)(i=1nai),而后者很容易在修改的时候维护。问题转化为了求区间 [ l − 1 , r − 1 ] [l-1,r-1] [l1,r1]的最大前缀异或和了。
只考虑前缀这是一个普通01字典树的问题。现在对于区间要用到可持久化01字典树,对每个前缀 i i i都维护一个新的字典树,对于没有走到的分支直接用历史版本。另外记录一下每个节点的个数。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll INF=LONG_LONG_MAX;
const int N=6e5+7;
int n,m;
int a[N],sum[N];
int rt[N],ch[N*31][2],cnt[N*31];
int tot=0; 
void insert(int i,int x) {
	int p=rt[i-1]; 
	int now=(rt[i]=++tot);
	for(int i=30;i>=0;i--) {
		int nxt=(x>>i)&1;
		ch[now][nxt]=++tot;
		ch[now][nxt^1]=ch[p][nxt^1];
		now=ch[now][nxt];
		p=ch[p][nxt];
		cnt[now]=cnt[p]+1;
	}
}
int query(int l,int r,int x) {
	r=rt[r],l=rt[l];  
	int ans=0;
	for(int i=30;i>=0;i--) {
		int nxt=(x>>i)&1;
		if(cnt[ch[r][nxt^1]]-cnt[ch[l][nxt^1]]>0) {
			ans+=(1<<i);
			l=ch[l][nxt^1];
			r=ch[r][nxt^1];
		}
		else {
			l=ch[l][nxt];
			r=ch[r][nxt];	
		}
	}
	return ans;
}
int main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) 
		scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)
		sum[i]=sum[i-1]^a[i];
	insert(0,0);
	for(int i=1;i<=n;i++) 
		insert(i,sum[i]);
	while(m--) {
		char opt[3];
		int l,r,x;
		scanf("%s",opt);
		if(opt[0]=='A') {
			n++;
			scanf("%d",&a[n]);
			sum[n]=sum[n-1]^a[n];
			insert(n,sum[n]);
		}
		else if(opt[0]=='Q') {
			scanf("%d%d%d",&l,&r,&x);
			x^=sum[n];
			l--;r--;
			printf("%d\n",query(l-1,r,x));
		}
	} 
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值