线段树 (Segment Tree)

概念引入

线段树和树状数组一样,是一个对区间进行操作的工具

之所以叫工具,因为线段树更像一个工具,而不是一个具体的算法

它比树状数组更加有用且有适配性,可以说大部分能用树状数组解决的问题都可以用线段树解决

线段树可以将O(N)转变为O(logN)

简单来说,线段树就是一颗二叉树,或者说是一个满二叉树

思路和树状数组差不多,也是分治的思想,将一段区间不断二分,通过二分的结果来合并成总结果

假设我们有一个有五个元素的数组,那么可以认为区间范围是[1,5]

那么就可以弄出这样一个线段树

image-20230101233413873

每一个区间都可以看成是一个二叉树的结点,这个结点所储存的信息就是该区间的信息

这些信息可以是区间和,最大值,最小值等等

举个例子:

要求[3,5]的和,我们可以吧[3,3]的和与[4,5]的和加在一起,这样效率就会变得很高

代码+基本操作

建树

image-20230101234337879

由图可以知道,或者说这是二叉树的特性:

某一结点的编号为 i , 那么它的左儿子的编号是 2i ,她的右儿子的编号是 2i+1

接下来我们就可以利用该特性来建树,值得一提的是,所开的空间长度是4倍数据的长度,具体为啥自行百度,不是重点

我们采用递归建树:

真正的建树函数

单点修改 + 区间查询

我们先讲区间查询

区间查询

如图,假如我们想要求得[1,5]的和,我们可以通过[1,3]以及[4,5]的和来求得

image-20230102152330018

由线段树的特性我们可以递归搜索只到找到该区间

Search()

单点修改

既然是单点修改,我们也就必须要找到具体的那个点

即假如我要修改第四个点,我就需要找到区间[4,4]

具体的路径如下图

image-20230102161228298

注意:

结点中存放的数据是区间和,所以要同步修改路径上的区间和

Change_point()

给出一道模板题, 可以试一下

P3374 【模板】树状数组 1

区间修改 + 区间查询

Lazy_Tag (无 push_down 版)

注:无 push_down版本只是一个中间产物,最终还是要加上push_down,但是了解无push_down可以加深理解

区间修改

首先要明确一点:

假设我们要对[1,4]内所有的数加一, 我们不可能通过上述的单点修改来实现,毕竟这样的复杂度过高

这个时候我们就需要引入一个“懒人标记” —— Lazy-tag

这个标记的意思是代表这个区间内每一个值都要加上某个值

先来说说这个思路的核心思想。

假设我们要想求得[1,3]的区间和

我们要获得[1,3]的sum,以及搜寻过程中的Lazy总值

然后sum + Lazy总值*区间内数的个数

image-20230103185340487

拿[1,3]举例,sum代表着不考虑[1,3]以及其之上Lazy标签的影响时,[1,3]的区间和

假设[1,3]原本和为10, 现在让[1,3]每个数+1,[1,3]的Lazy是1,sum却依然等于10

举个例子

十个元素分别是 1,2,3,4,5,6,7,8,9,10

初始时:[1,5]的Lazy值为0, sum为15 , [1,3]的Lazy值为0,sum为6

我们先让[1,3]区间内每个数加上1

那么此时[1,5]的Lazy值为0, sum却变成了18,[1,3]的Lazy值为0,sum为6

因为[1,5]包含[1,3] , 而[1,5]的sum是不考虑之后的lazy标签影响的,也就是[1,3]内区间的影响

但[1,3]区间确确实实是每个数加了一个1,为了让结果保持一致,我们也只能在[1,5]的sum上加上等价的数

 

接下来就是代码了:

前期准备

建树

区间修改

区间查询

下面有一道模板题,用上述知识可以完成

P3372 【模板】线段树 1

Lazy-Tag (push_down版)

基本思路

image-20230104140128867

在最开始的版本,我们要想知道一个区间确切的和需要上下两部分来完成

即该区间的sum值以及搜索该区间所途径的所有Lazy值得和

那么我们是否可以简化这一操作:

假设我们所途径得Lazy值均为0,那么我们求和就只需要一部分了

那么该如何让途径得Lazy为0呢

image-20230104140739486

我们可以通过标记下方得方法让途径得Lazy值为0

但这个时候原本区间得sum值就不正确了

这个时候我们就需要对原本区间的sum值进行更新

简单的想一下会知道:原区间的确切和等于它两个子区间的确切和相加

那它两个子区间的确切和是啥呢

是该区间原本的和在加上Lazy标签的影响

建树

建树的代码跟普通的一致

//基本的准备

//建树

区间修改

区间查询

注意:

在区间查询的时候,搜索过程中肯定会遇到Lazy值不为0的情况

这个时候我们也要将标签下放

之前那一道模板题,用push_down优化后

P3372 【模板】线段树 1