线段树是一种用于存储区间或段的树形数据结构,使得在其上进行快速的查询和修改操作成为可能。它常用于处理涉及区间的查询问题,如求区间和、区间最小值、区间最大值等。
基本概念
线段树是一棵二叉树,主要用于高效地解决一类区间查询问题。其每个节点表示一个区间,叶节点表示数组中的单个元素,内部节点表示其子节点区间的并集。
线段树的结构
- 叶节点:叶节点对应数组的单个元素。
- 内部节点:每个内部节点表示其子节点所代表的区间的并集。
例如,给定数组 [1, 3, 5, 7, 9, 11]
,其线段树的结构如下:
1 | [0, 5] |
构建线段树
线段树的构建时间复杂度为 O(n),其中 n 是数组的长度。构建过程如下:
- 递归分割区间:从数组的中间分割,将数组分为左右两个区间。
- 构建子树:对左右区间分别构建线段树。
- 计算节点值:内部节点的值由其左右子节点的值计算得到。
一般建树代码如下所示:
1 | void buildTree(LL k, LL l, LL r){ |
线段树的操作
- 单点/区间查询:查询某一范围内的数据,如区间和、区间最小值等。
- 单点/区间更新:更新数组中的某个/区间元素,并更新相关区间的信息。
一般在线段树中会实现如下函数:
query(left, right)
方法用于查询[left, right]
区间的和。update(index, value)
方法用于更新数组中index
位置的值,并更新相关区间的信息。pushDown(k)
用于下放节点k
上的lazyTag
上述代码实现见下面应用部分的例题。
线段树的应用
线段树广泛应用于解决以下问题:
- 区间求和
- 区间最小值/最大值查询
- 区间更新操作
- 动态顺序统计
通过线段树,可以在 O(log n) 时间复杂度内完成区间查询和更新操作,使其在处理动态数组问题时非常高效。
例题1:【模板】线段树1
线段树区间修改+区间查询模板题,实现代码如下:
1 | /********************************************************************************************************************** |
例题2:【模板】线段树2
此题同样是线段树区间修改+区间查询模板题,相比上一道模板题,难度主要加在了如何打lazyTag
上,本题涉及两种lazyTag(乘法,加法),解决这道题只需牢记下放lazyTag
时严格遵循先乘后加即可。
实现代码如下:
1 | /********************************************************************************************************************** |
注意事项
书写线段树的时候需要注意以下事项:
buildTree
和modify
函数在递归完成后的回溯阶段需要更新上层的统计值- 下放lazyTag所涉及的函数
pushDown
在query
和modify
中都是先调用再进行递归 - 加lazyTag标记以及区间更新函数
addTag
都是对当前区间进行操作(标记是加给儿子节点看的,方便后续pushDown
下去对儿子区间进行更新)
总结
线段树是一种强大的数据结构,特别适合于解决区间查询和更新问题。理解线段树的构建和操作原理,并掌握其实现方法,可以帮助我们高效地解决许多复杂的数据处理问题。