线段树区间修改+二进制 红球进黑洞

区间异或+查询,题目链接:https://ac.nowcoder.com/acm/contest/275/C

(tip需要前置技能区间修改)

题意:输入一段数,之后输入2区间则异或:选择l和r,对l,r之间的数进行异或操作,输入1则区间查询

题解

n,m<1e5,所以如果每次修改都异或到底logn*n*m绝壁t了,然后查了一下发现一个神奇的操作:

我们先开21个线段树(二进制21位表示作为异的数以及我们的数)

然后每个sum子节点存该位下1的个数

每个lazy子节点标记储存的是该节点是否被异或

如果作为异或数的k在该位是0,则不操作,因为0异或任何一个数都是数本身,如果是1则进行区间修改

下面是代码部分(对关键几步都有解释)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+70;
ll a[maxn];
int n,m;
typedef struct{
ll sum,lazy;
}point;
point tree[25][maxn<<2];
void up(int id,int rt)
{
tree[id][rt].sum=tree[id][rt<<1].sum+tree[id][rt<<1|1].sum;
}//统计子节点1的个数

void pd(int id,int l,int r,int rt)
{
if(tree[id][rt].lazy==1){//初始为0,因为^1^1还是原数所以这里lazy的0,1情况说明子节点是否需要修改
tree[id][rt].lazy=0;
tree[id][rt<<1].lazy^=1;
tree[id][rt<<1|1].lazy^=1;
int mid=(l+r)>>1;
tree[id][rt<<1].sum=(mid-l+1)-tree[id][rt<<1].sum;
tree[id][rt<<1|1].sum=(r-mid)-tree[id][rt<<1|1].sum;
}
}//对子节点进行区间修改和区间查询

void build(int id,int l,int r,int rt)
{
if(l==r){
tree[id][rt].sum=((a[l]>>id)&1);//找地id位是否是1
tree[id][rt].lazy=0;
return;
}
int mid=(l+r)>>1;
build(id,l,mid,rt<<1);
build(id,mid+1,r,rt<<1|1);
up(id,rt);
return;
}

void update(int id,int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R){
tree[id][rt].sum=(r-l+1)-tree[id][rt].sum;
tree[id][rt].lazy^=1;
return;
}//查询区间在已知区间内则直接修改:区间长度-原本的1的个数即为异或后的个数
int mid=(l+r)>>1;
pd(id,l,r,rt);//下推标记,下推子节点的1的数量以及lazy状态
if(L<=mid)update(id,L,R,l,mid,rt<<1);
if(R>mid)update(id,L,R,mid+1,r,rt<<1|1);
up(id,rt);
return;
}
ll query(int id,int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R){
return tree[id][rt].sum;
}
int mid=(l+r)>>1;
pd(id,l,r,rt);//每次查找修改前不要忘了下推标记
ll ans=0;
if(L<=mid) ans+=query(id,L,R,l,mid,rt<<1);//查询的时候我们统计该位下有多少个1
if(R>mid) ans+=query(id,L,R,mid+1,r,rt<<1|1);
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=0;i<=20;i++){
build(i,1,n,1);
}//10的5次方,这里搞了21位是防止临界出锅
while(m--){
ll t,l,r,k;
scanf("%lld",&t);
if(t==1){
scanf("%lld%lld",&l,&r);
ll ans=0;
for(int i=0;i<=20;i++){
ans+=1ll*query(i,l,r,1,n,1)*(1ll<<i);//1<<i会爆int
}
printf("%lld\n",ans);
}
else{
scanf("%lld%lld%lld",&l,&r,&k);
for(int i=0;i<=20;i++){
if((k>>i)&1){//这个&1很精髓
update(i,l,r,1,n,1);
}
}
}
}
return 0;
}

猜你喜欢

转载自www.cnblogs.com/ilikeeatfish/p/11502966.html