**思路如下:** ![](./images/segt2.png)![](./images/segt3.png)![](./images/segt4.png)
-此处给出 C++ 的代码实现,可参考注释理解:
+此处给出 C++ 的代码实现,可参考注释理解:
```cpp
void build(int s, int t, int p) {
上面那短短数行代码就能建立一个线段树。
-关于线段树的空间,如果采用堆式存储(堆式储存可以理解为 $2\times p$ 是 p 的左儿子, $2 \times p+1$ 是 p 的右儿子),d 数组的大小应为 $2n$ (叶子节点共有 $n$ 个,非叶子结点的个数不会超过叶子结点数量),上界是 $2n-1$。如果采用动态开点,则需要多开两个数组来记录左儿子和右儿子的编号/地址。
+关于线段树的空间: 如果采用堆式存储(堆式储存可以理解为 $2p$ 是 $p$ 的左儿子,$2p+1$ 是 $p$ 的右儿子),则 d 数组的长度应为 $2^{\left\lceil\log{n}\right\rceil+1}$,亦即取 $2$ 的幂中第一个大于等于 $n$ 的幂并将其乘二作为 d 数组的长度。
+
+分析: 容易知道线段树的深度是 $\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)),由于不会存在堆式储存中空置节点的问题,故叶子节点数量为 $n$,非叶子节点数量为 $n-1$,只需要开 $2n-1$ 大小的数组即可。缺点是由于不使用堆式储存,无法知道其左右儿子的编号,故须新增两个域来储存其左右儿子的编号。
### 线段树的区间查询
区间查询,比如求区间 $[l,r]$ 的总和(即 $a[l]+a[l+1]+ \cdots +a[r]$ )、求区间最大值/最小值……还有很多很多……怎么做呢?
-![](./images/segt6.png)
+![](./images/segt5.png)
拿上面这张图举栗!
-![](./images/segt7.png)
-
如果要查询区间 $[1,5]$ 的和,那直接获取 $d[1]$ 的值( $60$ )即可。那如果我就不查询区间 $[1,5]$ ,我就查区间 $[3,5]$ 呢?
傻了吧。但其实呢我们肯定还是有办法的!
你要查的不是 $[3,5]$ 吗?我把 $[3,5]$ 拆成 $[3,3]$ 和 $[4,5]$ 不就行了吗?
-此处给出 C++ 的代码实现,可参考注释理解:
+此处给出 C++ 的代码实现 ,可参考注释理解:
```cpp
int getsum(int l, int r, int s, int t, int p) {
如图:
+![](./images/segt6.png)
+
+![](./images/segt7.png)
+
![](./images/segt8.png)
![](./images/segt9.png)
![](./images/segt10.png)
-![](./images/segt11.png)
-
-![](./images/segt12.png)
-
注:这里 D 表示当前节点的值(即所表示区间的区间和)。
-为什么节点 A 的 D 是 $2\times (1000000000000001\bmod 2)$ 呢?原因很简单。节点 A 表示的区间是 $[1,2]$ ,一共包含 $2$ 个元素。我们是让 $[1,2]$ 这个区间的每个元素都加上 $1000000000000001\bmod 2$ ,所以节点 A 的值就加上了 $2\times (1000000000000001\bmod 2)$ 咯。
+为什么节点 A 的 D 是 $2\times (1000000000000001\bmod 2)$ 呢?原因很简单:节点 A 表示的区间是 $[1,2]$ ,一共包含 $2$ 个元素。我们是让 $[1,2]$ 这个区间的每个元素都加上 $1000000000000001\bmod 2$ ,所以节点 A 的值就加上了 $2\times (1000000000000001\bmod 2)$ 咯。
如果这时候我们要查询区间 $[1,1]$ (即节点 B 的值)怎么办呢?不是说了吗?如果 B 要用到的时候,A 就把它欠的还给 B!
具体是这样操作(如图):
-![](./images/segt13.png)
+![](./images/segt11.png)
-![](./images/segt14.png)
+![](./images/segt12.png)
注:为什么是加上 $1\times (1000000000000001\bmod 2)$ 呢?
原因和上面一样——B 和 C 表示的区间中只有 $1$ 个元素啊!
-![](./images/segt15.png)
+![](./images/segt13.png)
由此我们可以得到,区间 $[1,1]$ 的区间和就是 $1$ 啦!O(∩\_∩)O 哈哈~!
}
}
```
+
+## <span id="mempool">内存池</span>
+
+当我们需要动态分配内存的时候,频繁使用 new/malloc 会占用大量的时间和空间,甚至生成大量的内存碎片从而降低程序的性能,可能会使原本正确的程序 TLE/MLE。
+
+这时候我们就需要使用到「内存池」这种技巧: 在真正使用内存之前,先申请分配一定大小的内存作为备用,当需要动态分配时则直接从备用内存中分配一块即可。
+
+当然在大多数 OI 题当中,我们可以预先算出需要使用到的最大内存并一次性申请分配。
+
+如申请动态分配 $32$ 位有符号整数数组的代码:
+
+```cpp
+inline int* newarr(int sz){
+ static int pool[maxn],*allocp=pool;
+ return allocp+=sz,allocp-sz;
+}
+
+```
+
+线段树动态开点的代码:
+
+```cpp
+inline Node* newnode(){
+ static Node pool[maxn<<1],*allocp=pool-1;
+ return++allocp;
+}
+
+```
+