Loading

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

题意

给定长度为 \(n\) 的整数序列 \(a_1,a_2,\cdots,a_n\),要求支持 \(m\) 次操作,每次操作是以下两种之一:

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

\(1 \leq n,m \leq 5 \times 10^4,0 \leq a_i,v\leq 10^9,1 \leq l \leq r \leq n\)

题解

注意到区间异或之后可以维护的信息非常有限,考虑差分,记 \(b_i = a_i \operatorname{xor} a_{i-1}\)

那么区间异或相当于把 \(b_l,b_{r+1}\) 异或上 \(v\)

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

然后有一个聪明的性质,\(a_l,a_{l+1},\cdots,a_r\) 的线性基和 \(a_l,b_{l+1},\cdots,b_r\) 相同。

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

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

事实上这是非常简单的。对于 \(a_l\),就选 \(a_l\)。对于后面的,就是选 \(a_l\) 和一段 \(b_i\) 异或起来。

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

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

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

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

posted @ 2022-08-03 12:04  Meatherm  阅读(35)  评论(0编辑  收藏  举报