前言
如果你还不了解线段树的单点修改与区间查询,click here。
如果你还不了解线段树的区间修改与单点查询,click here。
实现
区间加和的区间查询线段树对于大家来说实现已经不难了,所以本篇以异或为例,着重介绍含有 lazy tag
的区间修改操作。
建树
没什么好讲的,和前面没有太大区别。
Code
void _Build(int k,int l,int r)
{
if(l==r)
{
sum[k]=a[l];
return;
}
int mid=l+r>>1;
_Build(k<<1,l,mid);
_Build(k<<1|1,mid+1,r);
sum[k]=sum[k<<1]+sum[k<<1|1];
}
区间修改
同样使用 lazy tag
优化时间复杂度,注意懒标记的更新是 add[k]^=1
,因为 add[k]
只有
0
0
0 和
1
1
1 两种状态,所以异或
1
1
1 就相当于取反,注意更新时需要 push_down
。
Code
void addin(int k,int l,int r) // 区间增加、更新
{
add[k]^=1;
sum[k]=(r-l+1)-sum[k];
}
void push_down(int k,int l,int r,int mid) //下压
{
if(add[k]==0) return;
addin(k<<1,l,mid);
addin(k<<1|1,mid+1,r);
add[k]=0;
}
void _Update(int k,int l,int r,int x,int y) // 加和
{
if(l>=x&&r<=y)
return addin(k,l,r);
int mid=l+r>>1;
push_down(k,l,r,mid);
if(x<=mid)
_Update(k<<1,l,mid,x,y);
if(mid<y)
_Update(k<<1|1,mid+1,r,x,y);
sum[k]=sum[k<<1]+sum[k<<1|1];
}
区间查询
同样使用分治的思想,在查询到该区间时就对区间进行 push_down
,从而达到优化时间复杂度的目的。
利用一个变量 res
将区间结果累加。
Code
int _Query(int k,int l,int r,int x,int y) //询问
{
if(l>=x&&r<=y)
return sum[k];
int mid=l+r>>1;
int res=0;
push_down(k,l,r,mid);
if(x<=mid)
res+=_Query(k<<1,l,mid,x,y);
if(mid<y)
res+=_Query(k<<1|1,mid+1,r,x,y);
return res;
}
经典例题
[TJOI2009] 开关
题目描述
现有 n n n 盏灯排成一排,从左到右依次编号为: 1 1 1, 2 2 2,……, n n n。然后依次执行 m m m 项操作。
操作分为两种:
- 指定一个区间 [ a , b ] [a,b] [a,b],然后改变编号在这个区间内的灯的状态(把开着的灯关上,关着的灯打开);
- 指定一个区间 [ a , b ] [a,b] [a,b],要求你输出这个区间内有多少盏灯是打开的。
灯在初始时都是关着的。
输入格式
第一行有两个整数 n n n 和 m m m,分别表示灯的数目和操作的数目。
接下来有 m m m 行,每行有三个整数,依次为: c c c、 a a a、 b b b。其中 c c c 表示操作的种类。
- 当 c c c 的值为 0 0 0 时,表示是第一种操作。
- 当 c c c 的值为 1 1 1 时,表示是第二种操作。
a a a 和 b b b 则分别表示了操作区间的左右边界。
输出格式
每当遇到第二种操作时,输出一行,包含一个整数,表示此时在查询的区间中打开的灯的数目。
样例 #1
样例输入 #1
4 5
0 1 2
0 2 4
1 2 3
0 2 4
1 1 4
样例输出 #1
1
2
提示
数据规模与约定
对于全部的测试点,保证 2 ≤ n ≤ 1 0 5 2\le n\le 10^5 2≤n≤105, 1 ≤ m ≤ 1 0 5 1\le m\le 10^5 1≤m≤105, 1 ≤ a , b ≤ n 1\le a,b\le n 1≤a,b≤n, c ∈ { 0 , 1 } c\in\{0,1\} c∈{0,1}。
分析
利用线段树维护每个区间中打开的灯的个数,将打开的灯状态设为 1 1 1,关闭的灯状态设为 0 0 0,则一个区间打开的灯的个数即为该区间的状态值加和。
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int _Read()
{
int f=1,s=0;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
s=s*10+(ch-48);
ch=getchar();
}
return s*f;
}
int n,m,a[100050];
int add[400050];
int sum[400050];
void _Build(int k,int l,int r)
{
if(l==r)
{
sum[k]=a[l];
return;
}
int mid=l+r>>1;
_Build(k<<1,l,mid);
_Build(k<<1|1,mid+1,r);
sum[k]=sum[k<<1]+sum[k<<1|1];
}
void addin(int k,int l,int r) // 区间增加、更新
{
add[k]^=1;
sum[k]=(r-l+1)-sum[k];
}
void push_down(int k,int l,int r,int mid) //下压
{
if(add[k]==0) return;
addin(k<<1,l,mid);
addin(k<<1|1,mid+1,r);
add[k]=0;
}
int _Query(int k,int l,int r,int x,int y) //询问
{
if(l>=x&&r<=y)
return sum[k];
int mid=l+r>>1;
int res=0;
push_down(k,l,r,mid);
if(x<=mid)
res+=_Query(k<<1,l,mid,x,y);
if(mid<y)
res+=_Query(k<<1|1,mid+1,r,x,y);
return res;
}
void _Update(int k,int l,int r,int x,int y) // 加和
{
if(l>=x&&r<=y)
return addin(k,l,r);
int mid=l+r>>1;
push_down(k,l,r,mid);
if(x<=mid)
_Update(k<<1,l,mid,x,y);
if(mid<y)
_Update(k<<1|1,mid+1,r,x,y);
sum[k]=sum[k<<1]+sum[k<<1|1];
}
signed main()
{
n=_Read();
m=_Read();
_Build(1,1,n);
while(m--)
{
int p,x,y;
p=_Read();
x=_Read();
y=_Read();
if(!p)
_Update(1,1,n,x,y);
else
printf("%lld\n",_Query(1,1,n,x,y));
}
return 0;
}