历史和线段树
前言
历史和线段树本质上就是新增了一些 \(\text{Tag}\) 的普通线段树,因其常常能够用于解决一类问题,所以被专门列出。
历史和线段树有着非常多不同的写法,在不同的题目上使用不同的写法可以大大减少编码难度。
本文主要从不同的角度去理解其作用和不同实现的适用范围。
这里是 \(\text{Deepseek}\) 整理的各种线段树学习笔记。
二维平面与时间轴
考虑如下问题:
有一个 \(n\times n\) 的二维平面,先进行 \(m\) 次矩形加操作,所有操作结束后,进行 \(q\) 次查询矩形和。
考虑对 \(x\) 坐标扫描线,于是先将所有的修改和查询进行差分,使得所有修改的右边界均为 \(n\),所有查询的左边界均为 \(1\)。
对于同一个 \(y\) 坐标,位于 \(x_c\) 的 \(+w\) 修改对位于 \(x_q\) 的询问所产生的贡献是 \((x_q-x_c+1)\times w\)。
拆开可得 \((x_q) \times w-(x_c-1) \times w\),那么对两部分分开维护,线段树维护扫描线上每个区间的 \(\sum x_q \times w\) 和 \(\sum w\) 即可。
于是可以做到 \(O(n\log_2 n)\)。
“历史”的含义
上述问题可以将 \(x\) 轴看作是时间轴,\(y\) 轴看作是需要维护的一个序列 \(a\)。
那么查询就是求序列上一个区间,之前所有历史版本的和,即:
其实就是模板题。
查询与修改交替的情形
本质上是再添加了一维操作顺序(这里和上文的时间轴是两个不同的概念),从原本的二维问题变成三维问题。
对操作顺序使用 \(\text{CDQ}\) 分治,每次考虑左边的修改对右边询问的贡献,套用历史和线段树的做法即可,不难做到 \(O(n\log_2^2 n)\)。
更一般的情况
容易发现,上面的维护方法强依赖于两个特点:
-
修改只有加法。
-
每次修改都会新建版本。
如果不符合这两个特点,就无法写出贡献式子,从而也无法像模板题那样简单的维护。
考虑维护一个序列,操作有三种:
-
区间加法。
-
新建版本,并继承原版本的值。
-
求区间历史和。
这种更一般的情况,需要我们在线段树上额外维护一个历史和 his 和更新历史和的次数和权值懒标记 t、lazh。
sum 表示区间和,lazp 表示加法懒标记。
每次区间 \(+w\):sum+=(r-l+1)*w,lazp+=w
每次更新历史和:his+=sum,lazh+=lazp,t++
每次 pushdown:sum'+=lazp*(r-l+1),his'+=t*sum+lazh*(r-l+1),t'+=t,lazp'+=lazp,lazh'+=lazh
矩阵形式
线段树上每个节点存行向量:
\begin{bmatrix}
\text{len} & \text{sum} & \text{his}
\end{bmatrix}
每次做区间加 \(w\) 等价于乘矩阵:
每次更新版本并统计历史和等价于乘矩阵:
那么直接把所有标记写成矩阵形式就可以维护历史和了,无需再去讨论复杂的 \(\text{Lazytag}\) 下放顺序,理解起来也会更容易。
但代价是常数非常大,在某些题目上可能无法通过。
当维护信息非常复杂时,可以先写成矩阵形式,如果要优化常数,再把矩乘展开对每一项进行维护即可,本质和直接加 \(\text{Tag}\) 一样。
\(\text{Tag}\) 更新顺序的本质
例题
主要记录一些对奇怪的东西做历史和的题目。
题目特征
不难发现,很多要用历史和的题目,其查询方式都是“矩形查询”、“一个区间的所有子区间”。
所以如果有这类关键词出现,可以考虑用历史和线段树维护。

浙公网安备 33010602011771号