题意:
给你n<=1e5个数a[i]和m<=5*1e4个操作,操作1要求输出[l,r]区间的数的和,操作2要求对[l,r]区间的数都去异或x。
思路:
线段树。对于异或操作,我们想到二进制数,只有当x的二进制位为1时,被异或的数对应的二进制位取反。对于求和操作,我们如果知道二进制数每位1的个数,我们累加起来就能得到其和了。所以这里对于每一位都建立一颗树来保存1的个数,这里a[i]、x<=1e6(2^20),所以我们需要建20棵树,然后只需要对每棵树进行简单的区间更新、区间求和即可。
#include<cstdio>
typedef __int64 LL;
const int MAX=1e5+5;
int sum[25][MAX<<2],lazy[25][MAX<<2];
int n,m,a[MAX];
void PushUp(int id,int rt){
sum[id][rt]=sum[id][rt<<1]+sum[id][rt<<1|1];
}
void PushDown(int id,int rt,int m){
if(lazy[id][rt]){
lazy[id][rt<<1]^=lazy[id][rt];
lazy[id][rt<<1|1]^=lazy[id][rt];
sum[id][rt<<1]=(m-(m>>1))-sum[id][rt<<1];
sum[id][rt<<1|1]=(m>>1)-sum[id][rt<<1|1];
lazy[id][rt]=0;
}
}
void Build(int id,int l,int r,int rt){
sum[id][rt]=lazy[id][rt]=0;
if(l==r){
sum[id][rt]=a[l]%2;
a[l]/=2;
//printf("l=%d a[l]=%d\n",l,a[l]);
return;
}
int mid=(l+r)>>1;
Build(id,l,mid,rt<<1);
Build(id,mid+1,r,rt<<1|1);
PushUp(id,rt);
}
void UpDate(int id,int L,int R,int l,int r,int rt){
if(L<=l&&r<=R){
lazy[id][rt]^=1;
//printf("before: %d\n",sum[id][rt]);
sum[id][rt]=(r-l+1)-sum[id][rt];
//printf("after: %d\n",sum[id][rt]);
//printf("l=%d r=%d\n",l,r);
return;
}
PushDown(id,rt,r-l+1);
int mid=(l+r)>>1;
if(L<=mid) UpDate(id,L,R,l,mid,rt<<1);
if(R>mid) UpDate(id,L,R,mid+1,r,rt<<1|1);
PushUp(id,rt);
}
int Query(int id,int L,int R,int l,int r,int rt){
if(L<=l&&r<=R){
return sum[id][rt];
}
PushDown(id,rt,r-l+1);
int res=0;
int mid=(l+r)>>1;
if(L<=mid) res+=Query(id,L,R,l,mid,rt<<1);
if(R>mid) res+=Query(id,L,R,mid+1,r,rt<<1|1);
return res;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=20;i++){
Build(i,1,n,1);
}
scanf("%d",&m);
int q,l,r,x;
while(m--){
scanf("%d",&q);
if(q==1){
scanf("%d%d",&l,&r);
LL ans=0,tmp=1;
for(int i=1;i<=20;i++){
int cnt=Query(i,l,r,1,n,1);
//printf("i=%d cnt=%d\n",i,cnt);
ans+=(LL)cnt*tmp;
tmp*=2;
}
printf("%I64d\n",ans);
}
else{
scanf("%d%d%d",&l,&r,&x);
for(int i=1;i<=20;i++){
if(x%2){
//printf("i=%d\n",i);
UpDate(i,l,r,1,n,1);
}
x/=2;
if(!x) break;
}
}
}
return 0;
}