## 动态开点线段树 我们发现,线段树的一次操作只与一个链有关,有一些节点可能是从头到尾都用不到的。所以我们可以在建树时,不开满 $4×n$ 的空间,而是在使用这个点的时候才建立这个点。 例如下面这张图: ![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/{year}/{month}/{md5}.{extName}/202308301653891.png) 我们首先存 $2$ ,先从根节点 $root$ 开始查找。 发现 $2$ 在 $1−5$ 中,我们发现并没有这个点,那么建立。 发现 $2$ 在 $1−3$ 中,我们发现并没有这个点,那么建立。 发现 $2$ 在 $1−2$ 中,我们发现并没有这个点,那么建立。 建立点 $2$。 接着存 $3$。 发现 $3$ 在 $1−5$ 中,我们发现已经有这个点,不进行操作。 发现 $3$ 在 $1−3$ 中,我们发现已经有这个点,不进行操作。 建立点 $3$。 以此类推。 最后我们发现,点 $1$,点 $4$,点 $6$ 没有被建立。这是数据小的情况,如果数据规模很大,省下的空间是非常可观的。 #### 动态开点适用于什么问题 动态开点一般在 $n$ 很大(例如 $n≤10^9$)的时候使用。 #### 带累加懒标记的动态开点线段树模板 ```cpp {.line-numbers} // 动态开点线段树 #define int long long #define ls tr[u].l #define rs tr[u].r #define mid ((l + r) >> 1) struct Node { int l, r; int sum, add; } tr[N << 1]; int root, idx; // 汇总统计信息 void pushup(int u) { tr[u].sum = tr[ls].sum + tr[rs].sum; } // 创建节点:节点号分配,懒标记初始化 void build(int &u) { if (u) return; u = ++idx; // tr[u].add = 0; } void pushdown(int &u, int l, int r) { if (tr[u].add == 0) return; // 如果没有累加懒标记,返回 build(ls); // 左儿子创建 build(rs); // 右儿子创建 // 懒标记下传 tr[ls].sum += tr[u].add * (mid - l + 1); // 区间和增加=懒标记 乘以 区间长度 tr[rs].sum += tr[u].add * (r - mid); tr[ls].add += tr[u].add; // 加法的懒标记可以叠加 tr[rs].add += tr[u].add; // 清除懒标记 tr[u].add = 0; } // 区间修改 void modify(int &u, int l, int r, int L, int R, int v) { build(u); // 动态开点 if (l >= L && r <= R) { // 如果区间被完整覆盖 tr[u].add += v; // 加法的懒标记可以叠加 tr[u].sum += v * (r - l + 1); // 区间和增加=懒标记 乘以 区间长度 return; } if (l > R || r < L) return; // 如果没有交集 // 下传懒标记 pushdown(u, l, r); // 分裂 modify(ls, l, mid, L, R, v), modify(rs, mid + 1, r, L, R, v); // 汇总 pushup(u); } // 区间查询 int query(int u, int l, int r, int L, int R) { if (l >= L && r <= R) return tr[u].sum; // 如果完整命中,返回我的全部 if (l > R || r < L) return 0; // 如果与我无关,返回0 pushdown(u, l, r); return query(ls, l, mid, L, R) + query(rs, mid + 1, r, L, R); } ``` 递归进入左右儿子,如果要用新点,就开新点。 **$Q1$:如果不动态开点是不是离散化一下就行?** **答**:理论上可以,但是有的时候强制在线,就不能离散化了 **$Q2$:为什么一定要传递引用呢?不传递引用为什么出错呢?** **答**:这是因为你向下传递$ls$时,其实$ls$可能还没创建,如果按$\&$地址传递$u$,那么一旦$ls$被创建新节点号时,会回写$tr[u].l$,建立起链条式关系。 如果你不用$\&$地址符传递$u$,那么即使 $ls$被成功创建了新的节点号,但是不知道是谁的左儿子,也就没法回写$tr[u].l$,无法建立起链条关系。 ### 练习题 #### [$P3372$ 【模板】线段树$1$](https://www.cnblogs.com/littlehb/p/16224904.html) **【黄海总结的最为完整的线段树+懒标记,动态开点线段树+懒标记的模板,十分经典,强烈推荐】** 打磨一个模板完美后,开始对以前做过的习题全面修改一遍,$OI$这东西太耗时间了。 #### [$CF915E$ $Physical$ $Education$ $Lessons$](https://www.cnblogs.com/littlehb/p/17664882.html) #### [$T125847$ 【模板】动态开点线段树](https://www.cnblogs.com/littlehb/p/17667050.html) **【懒标记线段树,动态+静态线段树实现】** #### [$P3373$【模板】线段树$2$](https://www.cnblogs.com/littlehb/p/17665847.html) **【双懒标记线段树,动态+静态线段树实现】** P3960 列队(动态开点线段树+主席树,未能理解,待研究) https://www.cnblogs.com/LawrenceSivan/p/14686207.html