牛客算法周周练11C - 红球进黑洞(二维线段树+区间异或+区间求和)

链接:https://ac.nowcoder.com/acm/contest/6046/C
来源:牛客网

时间限制:C/C++ 3秒,其他语言6秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

在心理疏导室中有一种奇特的疏导工具,叫做红球。红球被提前分为了许多正方形小方格。
每当有人来找ATB做心理疏导时,ATB就会让他去先玩红球,然后通过红球小格方的高度来判断一个人的压力程度的高低
具体地讲,ATB会让该人对于一个序列执行以下操作

  1. 区间求和,即输入l,r,输出xl+…xr的和
  2. 区间异或,即输入l,r,k,对于l ≤ i ≤ r,xi⊕k;
    可是ATB天天算计那么多答案,已经对这份工作产生了厌烦,所以请你帮帮他,对于一组给定的数据,输出对应的答案
    ATB会将你感谢到爆

输入描述:

第一行两个整数n和m,表示数列长度和询问次数
第二行有n个整数,表示这个数列的初始数值
接下来有m行,形如 1 l r 或者 2 l r k
分别表示查询

输出描述:

对于每一个查询操作,输出查询的结果并换行
示例1
输入
10 10
8 5 8 9 3 9 8 3 3 6
2 1 4 1
1 2 6
2 9 10 8
1 1 7
2 4 7 8
2 8 8 6
2 2 3 0
1 1 2
2 9 10 4
1 2 3
输出
33
50
13
13
备注:

  1. 数据范围
    对于30%30%的数据,保证 n, m, k≤ 10
    对于另外30%30%的数据,保证 n, m ≤ 50000, k ∈ {0, 1}
    对于全部100%100%的数据,保证 1 ≤ n,m ≤ 105, 0≤ ai,k ≤ 105
  2. 说明
    a⊕b表示a xor b

题目大意:

第一行给出n m,表示n个数和m个询问,对于接下来的m行

  • 1 l r 表示求区间[l,r] 的和
  • 2 l r k 表示区间[l,r] 内每个数都异或k。

解题思路:

二维线段树+区间异或+区间求和,lazy标记改一下即可,开一个tree[max*4][25]的数组模拟线段树,25表示存放1的个数,方便lazy数组使用,通过位运算推出每个node 的值来,先放代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int _max = 1e5+50;
typedef long long ll;
ll tree[_max<<2][25],lazy[_max<<2],a[_max];
void pushup(int node)
{
	for(int i=0;i<25;i++)
	  tree[node][i]=tree[node<<1][i]+tree[node<<1|1][i];
}
void build(int node,int l,int r)
{
	lazy[node]=0;
	if(l==r)
	{
		for(int i=0;i<24;i++)
		  ((ll)(1<<i)&a[l])?tree[node][i]=1:tree[node][i]=0;
		return;
	}
	int mid=(l+r)>>1;
	build(node<<1,l,mid);
	build(node<<1|1,mid+1,r);
	pushup(node);
}
void pushdown(int node,int l,int r)
{
	int mid=(l+r)>>1;
	if(lazy[node])
	{
		lazy[node<<1|1]^=lazy[node];
		lazy[node<<1]^=lazy[node];
		for(int i=0;i<25;i++)
		{
			int t=(lazy[node]>>i)&1;
			if(t)
			{
				tree[node<<1][i]=mid-l+1-tree[node<<1][i];
				tree[node<<1|1][i]=r-mid-tree[node<<1|1][i];
			}
		}
		lazy[node]=0;
	}
}
void update(int node,int l,int r,int st,int en,int val)
{
	int mid=(l+r)>>1;
	if(st<=l&&r<=en)
	{
		lazy[node]^=val;
		for(int i=0;i<25;i++)
		{
			int t=(val>>i)&1;
			if(t)
			  tree[node][i]=r-l+1-tree[node][i];
		}
		return ;
	}
	pushdown(node,l,r);
	if(st<=mid)
	  update(node<<1,l,mid,st,en,val);
	if(en>mid)
	  update(node<<1|1,mid+1,r,st,en,val);
	pushup(node);
}
ll query(int node,int l,int r,int st,int en)
{
	if(st<=l&&en>=r)
	{
		ll ans=0;
		for(int i=0;i<25;i++)
		  ans+=(ll)(1<<i)*tree[node][i];
		return ans;
	}
	pushdown(node,l,r);
	int mid=(l+r)>>1;
	ll ans1=0,ans2=0;
	if(st<=mid)
	  ans1=query(node<<1,l,mid,st,en);
	if(en>mid)
	  ans2=query(node<<1|1,mid+1,r,st,en);
	return ans1+ans2;
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	  scanf("%d",&a[i]);
	build(1,1,n);
	while(m--)
	{
		int f,l,r,k;
		scanf("%d",&f);
		if(f==1)
		{
			scanf("%d%d",&l,&r);
			cout<<query(1,1,n,l,r)<<endl;
		}
		else
		{
			scanf("%d%d%d",&l,&r,&k);
			update(1,1,n,l,r,k);
		}
	}
	//system("pause");
	return 0;
}

对pushdown函数的解释:pushdown向下更新,对于这里的for循环,异或的本质:1和0异或还是1,但和1异或就成了0。0和任何数异或都不变,所以0不考虑。也就是说,对于第 k 位,异或后1的个数 = 原来0的个数 = 区间长度(总个数)- 异或前1的个数, 所以就有了tree[node>>1][i] = mid-l+1-tree[node>>1][i]这个语句。

posted @ 2020-06-18 18:28  Hayasaka  阅读(85)  评论(0编辑  收藏  举报