目录
声明
线段树理解起来不难,但是题灵活多变,难,建议学之后多刷题
正文
区间问题和线段树
有一类区间问题可以抽象成如下模型。
给定包含 n 个数的数组 a1,a2,⋯an。有两种操作
-
查询区间 [l,r] 最小的数。
-
修改第 ai 为 x。
这里,为了解决这个问题,我们介绍一种灵活的数据结构——线段树。
我们用一棵二叉树来表示线段树,线段树中的每个结点都表示一个区间。每个非叶子结点都有左右两棵子树,分别对应区间的 "左半" 和 "右半"。为了方便起见,我们给根结点编号为 1。对于每个结点,其左结点的编号为 2i,其右结点的编号为 2i+1。
对于一个结点,如果其表示的区间为 [l,r]。分情况如果 l=r,那么这个是一个叶子结点。否则令mid=⌊(l+r)÷2⌋ (注意:非中括号,是下取整),左儿子对应的区间为 [l,mid],右儿子对应的区间为 [mid+1,r],这一思想有点类似二分。下面就是 n=10 的时候的线段树。
假定根结点表示长度为 2^h 的区间,不难发现,树的第 i 层有 2^i 个结点,每个结点对应一个长度为 2^(h-i) 的区间。最大层的编号为 h,结点总数为 1+2+4+8+⋯+2^h=2^(h+1)−1,略小于区间长度的两倍。而当整个区间长度不是 2 的整数幂时,虽然叶子结点不在同一层,但树的最大层编号和结点总数仍满足上述结论。
线段树的建立
前面构建的线段树,只是展示了线段树中各结点所对应的区间,但是对于用到线段树的大部分题目来说,这些线段所拥有的附加信息才是重头戏。比如要维护区间最小值问题,我们用一个额外的数组minv
记录每个结点对应的区间的最小值。
对于叶子结点,最小值就是一个数。而对于非叶子结点,区间的最小值就是左儿子的最小值和右儿子最小值中的最小值。
比如 n=10,a=1,3,5,7,9,10,2,4,8,6 的时候,对应的线段树如下
可以发现这个构建过程是一个递归的过程,父节点的信息需要用子节点去更新,所以我们需要先递归的构建好左右子树。见下面代码。
const int maxn = 10010;
int minv[4 * maxn], a[maxn];
// id 表示结点编号,l, r 表示左右区间
void build(int id, int l, int r) {
if