引入
例题:(洛谷 P3372 【模板】线段树 1)
已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数加上 k k k。
2.求出某区间每一个数的和。
我们可以使用树状数组来解决这道题,然而这次我们要回归正解了!我们要使用线段树!
线段树
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。——百度百科
实 际 应 用 时 一 般 还 要 开 4 N 的 数 组 以 免 越 界 ! 实际应用时一般还要开 4N 的数组以免越界! 实际应用时一般还要开4N的数组以免越界!
4 N 的 数 组 ! 4N 的数组! 4N的数组!
4 N 的 数 组 ! 4N 的数组! 4N的数组!
(血淋淋的教训)
线段树能够支持满足结合律的运算的区间操作,查改的时间复杂度均为 O ( l o g n ) O(logn) O(logn),与树状数组相比,它的功能更强大,但是具有常数大,码量大的缺点。对于此,你需要记住以下三点:
- 树状数组能解决的问题,线段树一定能解决;
线段树能解决的问题,树状数组不一定能解决。 - 在能用树状数组解决的情况下,尽量用树状数组解决;
在对常数要求高的情况下,尽量用树状数组解决。 - 线段树的代码较为复杂,写挂了的话建议重写。
好,那我们进入正题。
在解决上述区间查改的问题之前,我们先来看一下这个问题。
对于这道树状数组的模板题,我们可以使用较为简单的线段树来实现。
我们来看一下线段树的结构图:
我们将其填入数字:
其中每一个长条就是所谓的“线段”,可以发现,父节点(父线段)被平均分成两个子节点(子线段)。
线段树的原理是什么呢?我们来看上面的图,在实际操作中,上图的线段实际上是不存在的,那么存在的是什么呢?是线段内各数的和。
这样,我们可以发现,每个节点的值都等于其两个儿子的和,叶子节点的值为原数。
我们来模拟一下单点修改的过程:
我们要将第 3 3 3 个数加上 2 2 2,也就是将 4 4 4 加 2 2 2。
首先,修改叶子节点:
依次向上累加,注意,在实际操作中只是将父节点更新为子节点之和(push_up),而并不需要更新线段上的数值,这里是为了便于理解。
最后更新完是这样的:
那查询呢?
假设我们要求 [ 2 , 4 ] [2,4] [2,4] 的和,这时我们要做相反的操作,即从根开始。
首先,在根节点找到需操作的区间:
向下分割:
继续分割,直到分割到完整的线段,返回线段的值:
至此,我们已经解决了单点修改和区间