算法学习笔记【线段树】

目录

什么是线段树

线段树的作用

线段树的建立

区间修改

区间查询

线段树的代码综合实现


制作不易,点个赞再走吧

什么是线段树

线段树(Segment Tree)几乎是算法竞赛最常用的数据结构了,它主要用于维护区间信息(要求满足结合律)。与树状数组相比,它可以实现 O(log⁡n) 的区间修改,还可以同时支持多种操作(加、乘),更具通用性。

线段树就是一棵二叉平衡树,根节点代表某一段区间和,越往下区间越小,直到区间为的长度为1的时候结束(叶子节点)

我们可以通过模板题来了解线段树:

洛谷:P3372 【模板】线段树 1

线段树的作用

线段树的建立

线段树的建立可以通过依次遍历左右两边的子树,知道找到叶子节点,然后将对应的数值插入该节点上(注意一定要先遍历左子树,然后遍历右子树),插入后,然后将左右子树的值传给根节点,依次进行,那么我们所得到的线段树就是,根节点等于左右节点的和

那么我们现在讨论标号的问题,我们所建立的线段树对应的标号是怎样的?

我们是这样进行标号的,每遍历到一个点 u ,那么他的左子树就是 u * 2,那么它的右子树就是u * 2+1。

 那么它对应的区间又是怎样的呢?

我们就是这样进行对区间进行分配的。

那么区间求和又是怎样一回事呢?

 那么这些就是线段树的基本结构,详细细节,请看代码。

代码实现:

void pushup(int u)
{
	tree[u] = tree[u<<1] + tree[u<<1|1];
}
// 建树 
void buile(int l,int r,int u)
{
	 if(l == r) tree[u] = arr[l];
	 else {
	 	int mid = (l + r) >> 1;
	 	buile(l,mid,u<<1);
	 	buile(mid+1,r,u<<1|1);
	 	pushup(u);
	 }
}

区间修改

我们想进行区间修改的时候,我们需要先找到我们需要修改的区间位置,然后再进行修改,查找的思路请自己观看代码进行了解,这里就不多进行描述。

值得注意的是,如果我们每一次都直接对区间内的每一个叶子进行修改,那么时间复杂度就会大大提升,那么我们就想一种方法进行优化。我们想可以先不进行对每一个叶子进行修改,先对我们需要找的区间的范围进行修改,然后我们将这个区间对应的标号标记一下,并存下需要加的值,然后当下一次进行遍历,遍历到该点的左右节点的时候,就将该点标记的值加在下子树中,然后取消标记,这样,我们就不需要每次都遍历区间对应的每个点了。我们将这个方法叫做“lazy标记(懒标记)”

想必,大家看到这么多字都看蒙了,那么结合图来讲解大家一定喜欢。

这样就完成了区间修改。

实现代码:

//区间修改
void update(int l,int r,int x,int u,int cl,int cr)
{
	if(cl > r || cr < l) return ;
	if(cl >= l &&  cr <= r)
	{
		tree[u] += (cr - cl + 1) * x;
		if(cr > cl) lazy[u] += x;
	}
	else {
		int mid = (cl + cr) / 2;
		lazy[u<<1] += lazy[u];
		lazy[u<<1|1] += lazy[u];
		tree[u<<1] += lazy[u] * (mid -cl+1);
		tree[u<<1|1] += lazy[u] * (cr-mid);
		lazy[u] = 0;
		update(l,r,x,2*u,cl,mid);
		update(l,r,x,2*u+1,mid+1,cr);
		pushup(u);
	}
}

当然我们也可以将lazy的计算放入一个函数中,分块解决,可以更好理解

实现代码:

void push_down(int u,int len)
{
	lazy[u<<1] += lazy[u];
	lazy[u<<1|1] += lazy[u];
	tree[u<<1] += lazy[u] * (len-len/2);
	tree[u<<1|1] += lazy[u] * (len/2);
	lazy[u] = 0;
}
// 区间修改
void update(int l,int r,int x,int u,int cl,int cr)
{
	if(cl > r || cr < l) return ;
	if(cl >= l &&  cr <= r)
	{
		tree[u] += (cr - cl + 1) * x;
		if(cr > cl) lazy[u] += x;
	}
	else {
		int mid = (cl + cr) / 2;
		push_down(u,cr-cl+1);
		update(l,r,x,2*u,cl,mid);
		update(l,r,x,2*u+1,mid+1,cr);
		pushup(u);
	}
}

区间查询

根据上述对于线段树的解释,想必大家对与线段树有了基本的了解,那么对与这个区间查询,就更好理解了,

区间查询就是和区间修改差不多,一个是找了一个值,然后进行修改那个值,而区间查询则是返回这个值,也是使用递归的方式进行计算的。值得一提的是,当我们进行区间查询的时候也是需要进行lazy值的处理,因为,在区间修改的时候不一定能遍历到这个我们需要查询的这个点,所以,我们进行区间查询的时候也是需要进行lazy的处理。

其他的操作就和区间修改差不多了,这里就不多描述了,想必聪明的你一定能一眼看懂两者的区别和求区间和的实现方式。

实现代码

void push_down(int u,int len)
{
	lazy[u<<1] += lazy[u];
	lazy[u<<1|1] += lazy[u];
	tree[u<<1] += lazy[u] * (len-len/2);
	tree[u<<1|1] += lazy[u] * (len/2);
	lazy[u] = 0;
}
// 区间查询
int query(int l,int r,int u,int cl,int cr) {
	if(cl > r || cr < l) return 0;
	else if(cl >= l && cr <= r)
	{
		return tree[u];
	}
	else {
		int mid = (cl + cr) /2;
		push_down(u,cr-cl+1);
		return query(l,r,u<<1,cl,mid) + query(l,r,u<<1|1,mid+1,cr);
	}
}

线段树的代码综合实现

该代码对应的就是上面给出的链接里面的题,结合题目理解代码会事半功倍哈。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long

const int N = 100005;
int n,m;
int tree[4*N];
int arr[N];
int lazy[4*N];
void push_down(int u,int len)
{
	lazy[u<<1] += lazy[u];
	lazy[u<<1|1] += lazy[u];
	tree[u<<1] += lazy[u] * (len-len/2);
	tree[u<<1|1] += lazy[u] * (len/2);
	lazy[u] = 0;
}
void pushup(int u)
{
	tree[u] = tree[u<<1] + tree[u<<1|1];
}
// 建树 
void buile(int l,int r,int u)
{
	 if(l == r) tree[u] = arr[l];
	 else {
	 	int mid = (l + r) >> 1;
	 	buile(l,mid,u<<1);
	 	buile(mid+1,r,u<<1|1);
	 	pushup(u);
	 }
}

// 区间修改
void update(int l,int r,int x,int u,int cl,int cr)
{
	if(cl > r || cr < l) return ;
	if(cl >= l &&  cr <= r)
	{
		tree[u] += (cr - cl + 1) * x;
		if(cr > cl) lazy[u] += x;
	}
	else {
		int mid = (cl + cr) / 2;
		push_down(u,cr-cl+1);
		update(l,r,x,2*u,cl,mid);
		update(l,r,x,2*u+1,mid+1,cr);
		pushup(u);
	}
}
// 区间查询
int query(int l,int r,int u,int cl,int cr) {
	if(cl > r || cr < l) return 0;
	else if(cl >= l && cr <= r)
	{
		return tree[u];
	}
	else {
		int mid = (cl + cr) /2;
		push_down(u,cr-cl+1);
		return query(l,r,u<<1,cl,mid) + query(l,r,u<<1|1,mid+1,cr);
	}
}

signed main (void)
{
	cin >> n >> m;
	for(int i = 1; i <= n; i++)
	{
		scanf("%lld",&arr[i]);
	 } 
	 buile(1,n,1);
	while(m--)
	{
		int num;
		scanf("%lld",&num);
		if(num == 1){
			int a,b,c;
			scanf("%lld%lld%lld",&a,&b,&c);
			update(a,b,c,1,1,n);
		}
		else if(num == 2){
			int a,b;
			scanf("%lld%lld",&a,&b);
			cout << query(a,b,1,1,n) << endl;
		}
	}
	return 0;
 } 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值