题解 Luogu P5607 [Ynoi2013] 无力回天 NOI2017

题意

给定长度为 n 的整数序列 a1,a2,,an,要求支持 m 次操作,每次操作是以下两种之一:

  • 给定 l,r,v,将区间 [l,r] 异或上 v
  • 给定 l,r,v,查询区间 [l,r] 内选出任意个数的 ai,这些数和 v 的最大异或和(可以不选)

1n,m5×104,0ai,v109,1lrn

题解

注意到区间异或之后可以维护的信息非常有限,考虑差分,记 bi=aixorai1

那么区间异或相当于把 bl,br+1 异或上 v

考虑查询,可以用 0-1 Trie 或者线性基。但是区间 0-1 Trie 肯定存不下,于是只能线性基。

然后有一个聪明的性质,al,al+1,,ar 的线性基和 al,bl+1,,br 相同。

稍加观察就可以发现右边能表出的数一定是若干个 ai(lir) 异或起来,所以右边能被左边表出。

左边能表出的是若干个 ai 的异或和,那么只要能够证明右边可以表出任意一个 ai,就可以证明右边可以表出任意个 ai 的异或和。我们只需要把表出单个 ai 的方案拼到一起,最后只留下在方案中出现了奇数次的数,就得到了表出这些 ai 的方案。

事实上这是非常简单的。对于 al,就选 al。对于后面的,就是选 al 和一段 bi 异或起来。

然后?维护区间线性基,支持单点修改区间查询,因为两个线性基合并是 O(log2V) 的,于是复杂度是 O(nlognlog2V)

一个线性基小技巧:合并两个线性基的正确姿势是在一个里面插入另外一个,而不是建一个新的线性基憨憨地把两边都插进去,常数大到飞起。

甚至还有一种更优秀的写法。

inline void merge(Base &cur,Base lc,Base rc){
	cur.clear();
	for(int i=30;i>=0;--i)
		if(lc.a[i]) cur.a[i]=lc.a[i];
		else cur.a[i]=rc.a[i];
	for(int i=30;i>=0;--i) if(lc.a[i]&&rc.a[i]) cur.ins(rc.a[i]);
	return;
}

即:先尝试只用左边或右边的数表出某些位,对于那些两边都有值的位,再尝试两边的数都用,看能不能表出更多。

# include <bits/stdc++.h>

const int N=50010,INF=0x3f3f3f3f;

struct Base{
	int a[32];
	inline void ins(int x){
		for(int i=30;i>=0;--i){
			if(x&(1<<i)){
				if(a[i]) x^=a[i];
				else{
					a[i]=x;
					break;
				}
			}
		}
		return;
	}
	inline void clear(void){
		memset(a,0,sizeof(a));
		return;
	}
}tree[N<<2];
int xsum[N<<2];
int a[N],b[N];
int n,m;

inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-')f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}
inline int lc(int x){
	return x<<1;
}
inline int rc(int x){
	return x<<1|1;
}
inline void merge(Base &cur,Base lc,Base rc){
	cur.clear();
	for(int i=30;i>=0;--i)
		if(lc.a[i]) cur.a[i]=lc.a[i];
		else cur.a[i]=rc.a[i]; 
	for(int i=30;i>=0;--i) if(lc.a[i]&&rc.a[i]) cur.ins(rc.a[i]);
	return;
}
inline void pushup(int k){
	merge(tree[k],tree[lc(k)],tree[rc(k)]);
	xsum[k]=xsum[lc(k)]^xsum[rc(k)];
	return;
}
void build(int k,int l,int r){
	if(l==r){
		tree[k].ins(b[l]),xsum[k]=b[l];
		return;
	}
	int mid=(l+r)>>1;
	build(lc(k),l,mid),build(rc(k),mid+1,r);
	pushup(k);
	return;
}
void change(int k,int l,int r,int x,int v){
	if(l==r){
		tree[k].clear(),xsum[k]=(b[l]^=v),tree[k].ins(b[l]);
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) change(lc(k),l,mid,x,v);
	else change(rc(k),mid+1,r,x,v);
	pushup(k);
	return;
}
int queryval(int k,int l,int r,int L,int R){
	if(L<=l&&r<=R) return xsum[k];
	int mid=(l+r)>>1,res=0;
	if(L<=mid) res^=queryval(lc(k),l,mid,L,R);
	if(mid<R) res^=queryval(rc(k),mid+1,r,L,R);
	return res;
}
Base querybase(int k,int l,int r,int L,int R){
	if(L>R){
		return tree[0];
	}
	if(L<=l&&r<=R) return tree[k];
	int mid=(l+r)>>1;
	Base res;
	if((L<=mid)&&!(mid<R)) return querybase(lc(k),l,mid,L,R);
	if(!(L<=mid)&&mid<R) return querybase(rc(k),mid+1,r,L,R);
	merge(res,querybase(lc(k),l,mid,L,R),querybase(rc(k),mid+1,r,L,R));
	return res;
}

int main(void){
	n=read(),m=read();
	for(int i=1;i<=n;++i) a[i]=read(),b[i]=a[i]^a[i-1];
	build(1,1,n);
	int op,l,r,v;
	while(m--){
		op=read(),l=read(),r=read(),v=read();
		if(op==1){
			change(1,1,n,l,v);
			if(r<n) change(1,1,n,r+1,v);
		}else{
			Base res=querybase(1,1,n,l+1,r);
			int al=queryval(1,1,n,1,l);
			res.ins(al);
			for(int i=30;i>=0;--i) v=std::max(v,v^res.a[i]);
			printf("%d\n",v);
		}
	}

	return 0;
}

作者:Meatherm

出处:https://www.cnblogs.com/Meatherm/p/16546595.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Meatherm  阅读(40)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示