牛客算法周周练11C - 红球进黑洞(二维线段树+区间异或+区间求和)
链接:https://ac.nowcoder.com/acm/contest/6046/C
来源:牛客网
时间限制:C/C++ 3秒,其他语言6秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述
在心理疏导室中有一种奇特的疏导工具,叫做红球。红球被提前分为了许多正方形小方格。
每当有人来找ATB做心理疏导时,ATB就会让他去先玩红球,然后通过红球小格方的高度来判断一个人的压力程度的高低
具体地讲,ATB会让该人对于一个序列执行以下操作
- 区间求和,即输入l,r,输出xl+…xr的和
- 区间异或,即输入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
备注:
- 数据范围
对于30%30%的数据,保证 n, m, k≤ 10
对于另外30%30%的数据,保证 n, m ≤ 50000, k ∈ {0, 1}
对于全部100%100%的数据,保证 1 ≤ n,m ≤ 105, 0≤ ai,k ≤ 105 - 说明
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]这个语句。