Codeforces 242E-XOR on Segment (线段树 + 区间异或修改、求和)

在这里插入图片描述
题意:给你一个长度为n的序列,然后给出每个ai的值。有两种操作:
1 l r代表求出区间[l,r]内的ai的和。
2 l r x代表把区间[l,r]内的所有的 ai 变成 ai ^ x。
对于每个1操作输出查询的结果。

思路:看操作很明显就是线段树的操作。但是这里注意对于异或这个运算是不满足交换律的,也就是说 a ^ (b+c) != (a ^ c + b ^ c),那么我们就没办法直接对区间进行操作了。但是,看到异或就应该想到二进制有没有方法,很明显区间ai的和也等于把每一个ai二进制拆分后统计对应二进制位上的1的个数乘以位权求和。
于是,**我们考虑对于每一个二进制位去建一棵线段树来维护这个二进制位在不同区间内1的个数,也就可以求出来区间的和了。**至于修改操作,只有当x在二进制下第i位为1的时候,异或之后才会对这个位上的对应区间产生影响。可以产生影响时,就相当于在这个区间内做0和1的翻转操作,维护一下即可。
复杂度大约:O(m * 20 * logn),2e7左右吧。
比赛时没来得及敲

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 7;
int n,m;ll a[MAXN],cnt[22][MAXN];
//每一个二进制位上 用一棵线段树维护区间内的1的个数

struct Tree{//每个位维护一个 sum和lazy
	ll sum[MAXN<<2],lazy[MAXN<<2];
}tree[22];

void pushup(int id,int rt){
	tree[id].sum[rt] = tree[id].sum[rt<<1] + tree[id].sum[rt<<1|1];
}

void pushdown(int id,int rt,int tot){
	if(tree[id].lazy[rt]){
		tree[id].lazy[rt<<1] ^= tree[id].lazy[rt];
		tree[id].lazy[rt<<1|1] ^= tree[id].lazy[rt];
		tree[id].sum[rt<<1] = (tot-tot/2) - tree[id].sum[rt<<1];
		tree[id].sum[rt<<1|1] = (tot/2) - tree[id].sum[rt<<1|1];
		tree[id].lazy[rt] = 0; 
	}
}

void build(int id,int rt,int l,int r){
	tree[id].lazy[rt] = 0;
	if(l == r){
		tree[id].sum[rt] = cnt[id][l];
		return ;
	}
	int mid = (l+r)>>1;
	build(id,rt<<1,l,mid);build(id,rt<<1|1,mid+1,r);
	pushup(id,rt);
}

void modify(int id,int rt,int l,int r,int L,int R){
	if(l >= L && r <= R){
		tree[id].lazy[rt] ^= 1;//异或之后 为1则代表需要翻转一下 为0的话就是翻转了偶数次 相当于这个区间内的01没有反转。
		tree[id].sum[rt] = r - l + 1 - tree[id].sum[rt];
		return ;
	}
	pushdown(id,rt,r-l+1);
	int mid = (l+r)>>1;
	if(L <= mid) modify(id,rt<<1,l,mid,L,R);
	if(R > mid) modify(id,rt<<1|1,mid+1,r,L,R);
	pushup(id,rt);
}

int query(int id,int rt,int l,int r,int L,int R){
	if(l >= L && r <= R) return tree[id].sum[rt];
	pushdown(id,rt,r-l+1);
	int mid = (l+r)>>1;int ans = 0;
	if(L <= mid) ans += query(id,rt<<1,l,mid,L,R);
	if(R > mid) ans += query(id,rt<<1|1,mid+1,r,L,R);
	return ans;
}

int main(){
	scanf("%d",&n);
	for(int i = 1;i <= n;i ++){
		scanf("%d",&a[i]);
	}
	for(int i = 1;i <= n;i ++){
		for(int j = 0;j <= 21;j ++){
			cnt[j][i] = (a[i]>>j) & 1;
		}
	}
	for(int i = 0;i <= 21;i ++) build(i,1,1,n);
	scanf("%d",&m);
	int op,l,r;ll x;
	while(m--){
		scanf("%d",&op);
		if(op == 1){
			scanf("%d%d",&l,&r);
			ll ans = 0;
			for(int i = 0;i <= 21;i ++){
				ans += query(i,1,1,n,l,r) * (1ll<<i);			
			}
			printf("%lld\n",ans);
		}
		else if(op == 2){
			scanf("%d%d%lld",&l,&r,&x);
			for(int i = 0;i <= 21;i ++){
				if((x>>i)&1){
					modify(i,1,1,n,l,r);
				}
			}
		}
	}
	return 0;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值