题意
给定长度为 \(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;
}