线段树v2.0

前言

线段树,万金油数据结构。

线段树只会保留重要的、具有“代表性”的区间,来优化区间查询。

简介

下图(有点儿粗糙,请不要介意):

sewgment-tree.bmp

黑数字代表编号,红色的区间代表节点管辖的区间,至于绿色和黄色的 X 会在后面讲。

建树

Lemma 1:线段树至多有 O(logN)

我们从最下面开始看。

每往上一层,区间的数量大约会减半(实际上向下取整),要把节点数量减半到 1,也就需要 O(logN) 层。

在最坏情况下,每一层都会维护整个数组。所以直接对于整个数组暴力的复杂度也会是 O(NlogN) 的。

但是,我们知道,线段树的核心,是拼,是凑啊!

Lemma 2:线段树有 O(N) 个节点

每一个节点的作用其实就是把两个区间合并成一个,所以叶子节点有 N 个,非叶子节点有 N1 个,总共 2N1 个,也就是 O(N)

所以,对于叶节点,我们直接把信息传上来;对于非叶子节点,我们用其两个儿子节点的信息合并。

区间查询

Lemma 3:对于一个区间,每层至多有 2 个节点所管辖的区间是这个区间的子区间,并且这个节点的父亲的区间不是这个区间的子区间(这是“终止节点”的定义)。
Lemma 3.1:对于一个终止节点,它的左/右边至少有一边的终止节点的深度比它低

反证。

如果两边的终止节点都比它高,那就违反了线段树的“二叉”的结构(无法解释这个终止节点到底“在哪儿”)。

证明了 Lemma 3.1 之后,就非常简单了:

还是反证。

如果有某一层有 3 个或更多终止节点,有两种情况:

  • 有两个终止节点相邻,这违反了终止节点的定义;

  • 没有两个终止节点相邻,这违反了 Lemma 3.1。

那怎么找终止节点?

直接搜索,搜到一个终止节点就返回。

segment-tree-query(2,6).bmp

区间修改

还记得前面提到的黄色 X 和绿色 X 吗(好像说反了)?这两个 X 就是实现快速区间修改的关键。

懒标记

仍然拿 [2,6] 举例子,我们先停至 9[2,2],将这个节点的信息更新;

然后,当我们访问到 5[3,4] 时,我们终止,然后修改绿 X。

如此往复,不要忘记更新信息。

当我们查询 [3,3]

往下搜到 5[3,4] 的时候,重头戏来了:

我们下传 5[3,4] 的懒标记,更新 10[3,3]11[4,4] 的信息。

然后就可以安全的访问 10[3,3] 而不会出现错误的信息了。

永久标记

永久标记就是上面的黄 X,当我们访问到一个结点的时候我们不下传标记,而是把这个标记的贡献直接加进去。

注意修改时仍然要更新信息。

你可以把两种标记这样理解:

  • 懒标记像是一个上司,他会在领导视察的时候清理账单,还清工资;

  • 永久标记像是一个上司,但是他不会还清工资,而是伪造账单。

线段树分治

其实线段树分治的思想很简单,就是把某一个信息的所在区间当作一次区间修改,用永久标记的方式加入一棵在时间轴上建立的线段树。

当要回答某些时间点的信息的时候,我们一次性处理:

在线段树上 DFS,遇到了新信息就加入贡献,当回溯的时候撤销贡献。

就这么简单。

但由于贡献需要撤销,所以可能得使用可持久化数据结构。

可持久化线段树

好,现在我要保存线段树的历史版本。

所以我们需要可持久化线段树。

可持久化线段树只支持单点修改,但是仍然支持区间查询。

使用最经典的 Path Copying 思想,我们把修改的节点复制一份出来,然后就可以达到可持久化的目的了。

线段树能塞的各种奇奇怪怪的东西

线段树里面塞矩阵...还是 dp 矩阵

这个法子有个别名叫“动态 dp”。

posted @   hhc0001  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示