线段树入门

线段树是个好东西,首先要知道这些点:

1.线段树适用于任何区间修改和区间查询的操作,复杂度只有 O(logn) 贼快

2.线段树没有树状数组好写呢,其实也不难

3.线段树每一个点是管理一个区间!!

4.线段树不是所有节点都代表原数组的一个值,他可能会代表一个区间段中你想要的值

基础理论

首先上一个图

线段树

简单了解一下线段树

发现每一个节点的管理区间就是左右子所管理的区间相加

再看下一张图

将节点按照层序遍历编好号,发现:

一个节点的左孩子是它的编号的两倍,它的右孩子的编号是它的两倍加一

叶子节点管理的区间就只有它自己


发现问题

如果你使用前缀和去完成线段树的题目线段树模板1的话,你会发现:我c,还有修改!还特么是区间修改。

然后你会愉快地 TLE 然后放弃并失去信心。

下面就需要线段树了

线段树进行单点修改时,其他子树上的节点根本就不用动!改它干嘛!

例如,你要改原数组中的第6个↓

那么你只用去改变节点 18、12、6、3、1 的区间和的值不就完了!什么4、5、10、11,一点关系都没有!

区间更改也能解决,不过需要一些其他的东西。

实现

首先看一看如何建树!

本文只介绍静态建点、递归建树!

先定义一个结构体:

struct st
{
	int s;
	int l,r;
   	int lazy;
}

这个结构体就是一个节点!

l 和 r 分别代表这个节点管理的区间的左端点与右端点

s 是指它这个节点所管理的区间的和是多少

建树部分:

void buildtree(int i,int left,int right)
{
	t[i].l=left;
	t[i].r=right;
	if(left==right)
	{
		t[i].s=a[left];
		return ;
	}
	int mid=(left+right)/2;
	buildtree(lson,left,mid);
	buildtree(rson,mid+1,right);
	t[i].s=t[lson].s+t[rson].s;
}

首先:

参数left和right代表即将建树的部分

i代表你已经到的节点编号

接下来就可以快乐的把节点 i 的 l 和 r 赋值为 left 和 right了

如果i为叶子结点,也就是left==right时,直接把它的区间和赋值为a[left]

为什么是a[left]呢?

因为到了叶子结点,他所管理的就是他自己

算了,还是来张图吧:

比如说你要建第11个节点

看他的 l 和 r

发现都为5

所以直接赋值吧!毕竟它就是管理数组中下标为5的数

然后继续往下建树

根据上面的那张图可以发现:

他的左儿子管理的区间是 l 到 (l + r) / 2 ,右儿子是 (l+r)/2+1 到 r

所以向下建树吧

最后一步将左儿子的区间和加上右儿子的区间和就好了!

单点修改

在这里只阐述原理,不放出代码

先找到一个数

比如说第5个数:

更改以后也会更改的点有:5 2 1

所以只用把这些节点更改就完了

找到这个节点,再往根节点上去,将路途中的所有节点pushup一遍就完了

区间修改

讲区间修改之前,先简单讲讲懒惰标记

懒惰标记

懒惰标记是一个很好的东西,它好就好在可以降低区间修改的复杂度

具体是怎么用,是什么,接着看:


比如说我要修改区间26(居然一直在用一个图,我真勤奋~)

我都懒得上图了,直接翻翻上面的

我们会发现第9,5,18个节点会被覆盖

再看一眼结构体:

struct st
{
	int s;
	int left;
	int right;
	int lazy;
}t[maxn*4];

有没有好奇那个lazy是干什么用的

那个lazy就是懒惰标记

当你发现当前节点所管理的区间已被需要修改的区间覆盖

立即停止,将懒惰标记加上你所需要增加的值,再把这个节点所管理的区间和更新一下,然后回溯

代码:

void pushdown(int i)
{
	if(t[i].lazy==0) return; 不需要更新
	if(t[i].left==t[i].right)
	{
		t[i].lazy=0; 已经到了叶子节点,结束。因为已经改过了,下面也没有左儿子或右儿子了
		return ;
	}
	t[lson].s+=(t[lson].right-t[lson].left+1)*t[i].lazy;将左子更改
	t[lson].lazy+=t[i].lazy;懒惰标记继承给左子,为了以后将左子的左子和右子pushdown更新
	t[rson].s+=(t[rson].right-t[rson].left+1)*t[i].lazy;同理
	t[rson].lazy+=t[i].lazy;
	t[i].lazy=0;
}
void change(int i,int left,int right,int d) d为需要加上的数值
{
	pushdown(i);
	if(t[i].left==left && t[i].right==right)
	{
		t[i].lazy=d;
		t[i].s+=(right-left+1)*d;所需要加上的和为:数量*需要加上的数值
		return ;
	}
	t[i].s+=(right-left+1)*d;
	if(t[lson].right>=right) change(lson,left,right,d);判断左子或右子是否有一部分被需要更改的区间覆盖
	else if(t[rson].left<=left) change(rson,left,right,d);
	else change(lson,left,t[lson].right,d),change(rson,t[rson].left,right,d);
}

如果你要用到i这个节点,你需要先pushdown一下,因为它的左儿子和右儿子都没有更新,所以需要先pushdown更新一下

区间更改 完结撒花★,°:.☆( ̄▽ ̄)/$:.°★

单点查询

与单点更改差不多,自己想一想吧(懒得写了 比较简单)

区间查询

最后一个重点

还是看以前的图吧 ε=ε=ε=┏(゜ロ゜;)┛

接下来查询区间2~6

还是上一张图吧:

需要查询节点17,9,5,18的值

然后向上传递和

代码:

int query(int i,int l,int r)
{
	pushdown(i);
	if(t[i].left>=l&&t[i].right<=r)
		return t[i].s;
	int e=0;
	if(t[lson].right>=left)判断左子是否有覆盖部分 
		e+=quert(lson,l,r);有就加上 
	if(t[rson].left<=right)判断右子是否有覆盖部分 
		e+=query(rson,l,r);有就加上 
	return e;上传 
}

题目:

简单线段树

全部完结撒瓜!!

题目解析我会再写一篇blog

posted @   Y2y7m  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示