线段树:区间修改与区间查询

前言

如果你还不了解线段树的单点修改与区间查询,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 项操作。

操作分为两种:

  1. 指定一个区间 [ a , b ] [a,b] [a,b],然后改变编号在这个区间内的灯的状态(把开着的灯关上,关着的灯打开);
  2. 指定一个区间 [ 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 2n105 1 ≤ m ≤ 1 0 5 1\le m\le 10^5 1m105 1 ≤ a , b ≤ n 1\le a,b\le n 1a,bn 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;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值