OSDN Git Service

Update segment.md
author雷蒻 <34390285+hsfzLZH1@users.noreply.github.com>
Fri, 2 Aug 2019 13:39:11 +0000 (21:39 +0800)
committerGitHub <noreply@github.com>
Fri, 2 Aug 2019 13:39:11 +0000 (21:39 +0800)
docs/ds/segment.md

index 34cb67b..bf9e623 100644 (file)
@@ -10,19 +10,17 @@ author: CJSoft, HeRaNO, konnyakuxzy, hsfzLZH1
 
 ### 线段树的基本结构与建树
 
-有个大小为 $5$ 的数组 $a=\{10,11,12,13,14\}$ 要进行区间求和操作,现在我们要怎么把这个数组存到线段树中(也可以说是转化成线段树)呢?有以下做法:设线段树的根节点编号为 $1$ ,用数组 $d$ 来保存我们的线段树, $d[i]$ 用来保存线段树上编号为 $i$ 的节点的值(这里每个节点所维护的值就是这个节点所表示的区间总和),如图所示:
+有个大小为 $5$ 的数组 $a=\{10,11,12,13,14\}$ ,要将其转化为线段树,有以下做法:设线段树的根节点编号为 $1$ ,用数组 $d$ 来保存我们的线段树, $d[i]$ 用来保存线段树上编号为 $i$ 的节点的值(这里每个节点所维护的值就是这个节点所表示的区间总和),如图所示:
 
 ![](./images/segt1.png)
 
 图中 $d[1]$ 表示根节点,紫色方框是数组 $a$ ,红色方框是数组 $d$ ,红色方框中的括号中的黄色数字表示它所在的那个红色方框表示的线段树节点所表示的区间,如 $d[1]$ 所表示的区间就是 $[1,5]$ ( $a[1],a[2], \cdots ,a[5]$ ),即 $d[1]$ 所保存的值是 $a[1]+a[2]+ \cdots +a[5]$ , $d[1]=60$ 表示的是 $a[1]+a[2]+ \cdots +a[5]=60$ 。
 
-通过观察我们不难发现, $d[i]$ 的左儿子节点就是 $d[2\times i]$ , $d[i]$ 的右节点就是 $d[2\times i+1]$ 。进一步观察,可以看出如果 $d[i]$ 表示的是区间 $[s,t]$ (即 $d[i]=a[s]+a[s+1]+ \cdots +a[t]$ ) 的话,那么 $d[i]$ 的左儿子节点表示的是区间 $[ s, \frac{s+t}{2} ]$ , $d[i]$ 的右儿子表示的是区间 $[ \frac{s+t}{2} +1,t ]$ 。
+通过观察不难发现, $d[i]$ 的左儿子节点就是 $d[2\times i]$ , $d[i]$ 的右儿子节点就是 $d[2\times i+1]$ 。如果 $d[i]$ 表示的是区间 $[s,t]$ (即 $d[i]=a[s]+a[s+1]+ \cdots +a[t]$ ) 的话,那么 $d[i]$ 的左儿子节点表示的是区间 $[ s, \frac{s+t}{2} ]$ , $d[i]$ 的右儿子表示的是区间 $[ \frac{s+t}{2} +1,t ]$ 。
 
 具体要怎么用代码实现呢?
 
-我们继续观察,有没有发现如果 $d[i]$ 表示的区间大小等于 $1$ (区间大小指的是区间包含的元素的个数,即 $a$ 的个数)的话(设 $d[j]$ 表示区间 $[s,t]$ ,它的区间大小就是 $t-s+1$ ),那么 $d[i]$ 所表示的区间 $[s,t]$ 中肯定有 $s=t$ ,且 $d[i]=a[s]=a[t]$ 。
-
-这就是线段树的递归边界。
+我们继续观察,有没有发现如果 $d[i]$ 表示的区间大小等于 $1$ 的话(区间大小指的是区间包含的元素的个数,即 $a$ 的个数。设 $d[j]$ 表示区间 $[s,t]$ ,它的区间大小就是 $t-s+1$ ),那么 $d[i]$ 所表示的区间 $[s,t]$ 中肯定有 $s=t$ ,且 $d[i]=a[s]=a[t]$ 。这就是线段树的递归边界。
 
  **思路如下:** 
 
@@ -52,7 +50,7 @@ void build(int s, int t, int p) {
 
 分析:容易知道线段树的深度是 $\left\lceil\log{n}\right\rceil$ 的,则在堆式储存情况下叶子节点(包括无用的叶子节点)数量为 $2^{\left\lceil\log{n}\right\rceil}$ 个,又由于其为一棵完全二叉树,则其总节点个数 $2^{\left\lceil\log{n}\right\rceil+1}-1$ 。当然如果你懒得计算的话可以直接把数组长度设为 $4n$ ,因为 $\frac{2^{\left\lceil\log{n}\right\rceil+1}-1}{n}$ 的最大值在 $n=2^{x}+1(x\in N_{+})$ 时取到,此时节点数为 $2^{\left\lceil\log{n}\right\rceil+1}-1=2^{x+2}-1=4n-5$ 。
 
-而如果采用动态开点的方法(不一次性开出全部节点的内存,而是在第一次使用到一个空节点时才开出其内存,这样可以减少空间复杂度的常数。动态开点的方法见[内存池](../../intro/common-tricks/#mempool)),由于不存在堆式储存中空置节点的问题,不一定会使用所有节点,但是使用时数组的大小仍需设为 $4n$ 。缺点是由于不使用堆式储存,无法知道其左右儿子的编号,故须新增两个域来储存其左右儿子的编号。
+而如果采用 **动态开点** 的方法(不是一次性开出全部节点的内存,在第一次访问到一个空节点时才开出其内存,这样可以减少空间占用。动态开点的方法见[内存池](../../intro/common-tricks/#mempool)),由于不存在堆式储存中空置节点的问题,不一定会使用所有节点,但是使用时数组的大小仍需设为 $4n$ 。缺点是由于不使用堆式储存,无法知道其左右儿子的编号,故须新增两个域来储存其左右儿子的编号。
 
 ### 线段树的区间查询