线段树的各种姿势

普通线段树

线段树可以维护序列。一棵线段树长这样:

 1 2 3 4 5
|---------|
     / \
|-----|---|
   / \ / \
|---|-|-|-|
 / \
|-|-|

容易发现,线段树是一棵二叉树。线段树的每个节点都对应原序列的一段区间,且每个节点的左儿子和右儿子对应的区间长度几乎相等

具体地说,假设当前节点对应区间 \([l,r]\),则其左儿子对应区间 \([l,\lfloor \dfrac{l+r}{2} \rfloor]\),右儿子对应区间 \([\lfloor \dfrac{l+r}{2} \rfloor+1,r]\)。如果 \(l=r\),说明当前节点为叶子节点。

考虑如何分配节点的编号,这里我们可以令根结点的编号为 \(1\)\(i\) 号节点的左儿子编号为 \(2i\),右儿子编号为 \(2i+1\),容易发现这样分配编号是对的,注意此时数组的空间需要开到 \(4n\),否则可能会出现越界等情况。

(待填)

懒标记与标记永久化

(待填)

非递归线段树

(待填)

单侧递归线段树

(待填)

区间最值操作

(待填)

可持久化线段树

(待填)

区间历史查询

先说历史版本和。

以区间加操作为例,我们需要维护一个长度为 \(n\) 的序列 \(a\) 和一个长度为 \(n\) 的初始全为 \(0\) 的序列 \(s\),维护以下三种操作:

  • 首先 \(\forall i \in [l,r] , a_i \leftarrow a_i + x\),接着 \(\forall i \in [1,n] , s_i \leftarrow s_i + a_i\)

  • 查询 \(\sum\limits_{i=l}^r a_i\)

  • 查询 \(\sum\limits_{i=l}^r s_i\)

这里的 \(s_i\),也就是我们说的历史版本和,我们要做的就是在维护 \(a\) 的同时维护 \(s\),这里直接套用传统的线段树并不行,我们需要使用别的方法维护。

先来一个极端暴力的做法。我们考虑对于节点 \(i\) 需要维护什么信息,假设其对应的区间为 \([l_i,r_i]\),则我们一定需要维护 \(sum_i=\sum\limits_{i=l_i}^{r_i} s_i\) 以及 \(num_i=\sum\limits_{i=l_i}^{r_i} a_i\),考虑一次修改操作产生的影响,不妨令 \(len_i=r_i-l_i+1\),则一次区间加操作产生的影响为:

  • 首先 \(num_i \leftarrow num_i+len_i \times x\),接着 \(sum_i \leftarrow sum_i + num_i\)

我们不妨记修改后的数为 \(sum_i'\)\(num_i'\),则可以得到:

  • \(num_i' = num_i + len_i \times x\)

  • \(sum_i' = sum_i + num_i + len_i \times x\)

发现这个东西容易用矩阵维护。对于节点 \(i\) 考虑维护一个矩阵 \(\begin{bmatrix} sum_i & num_i & len_i\end{bmatrix}\),则一次区间修改容易用下面的式子刻画:

\[\begin{bmatrix} sum_i & num_i & len_i \end{bmatrix} \times \begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 0 \\ x & x & 1 \end{bmatrix} = \begin{bmatrix} sum_i' & num_i' & len_i \end{bmatrix} \]

我们只需要维护一棵支持区间乘矩阵的线段树即可,这一点是简单的。

但是我们还有一个问题:对于没有进行修改的区间,其历史和不会被更新,这个时候我们需要手动更新。你当然可以选择区间加 \(0\),但是这种方法的适配性不高。我们采用下面的式子:

\[\begin{bmatrix} sum_i & num_i & len_i \end{bmatrix} \times \begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} = \begin{bmatrix} sum_i' & num_i' & len_i \end{bmatrix} \]

欸你这个怎么和区间加 \(0\) 一模一样啊!但是不同的是,对于所有没有操作到的位置,都可以用这个矩阵转移。

于是我们就解决了这个问题,复杂度 \(O(3^3 n \log n)\)

当然这个做法是我口胡的。

但是不要紧,我们来看新的问题,现在我们需要维护一个长度为 \(n\) 的序列 \(a\) 和一个长度为 \(n\) 的初始全为 \(0\) 的序列 \(s\),维护以下三种操作:

  • 首先 \(\forall i \in [l,r] , a_i \leftarrow x\),接着 \(\forall i \in [1,n] , s_i \leftarrow s_i + a_i\)

  • 查询 \(\sum\limits_{i=l}^r a_i\)

  • 查询 \(\sum\limits_{i=l}^r s_i\)

你考虑此时的操作形如:

  • \(num_i' = len_i \times x\)

  • \(sum_i' = sum_i + len_i \times x\)

接下来直接把矩阵的维护往里面套一下即可。

\[\begin{bmatrix} sum_i & num_i & len_i \end{bmatrix} \times \begin{bmatrix} 1 & 0 & 0 \\ 0 & 0 & 0 \\ x & x & 1 \end{bmatrix} = \begin{bmatrix} sum_i' & num_i' & len_i \end{bmatrix} \]

当然对于没有修改的位置是这样的:

\[\begin{bmatrix} sum_i & num_i & len_i \end{bmatrix} \times \begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} = \begin{bmatrix} sum_i' & num_i' & len_i \end{bmatrix} \]

于是你得到了一个口胡的大常数做法。

当然你可以在矩阵具有特殊性质的时候采用特殊方法维护,仍然以区间加为例。你考虑如下的矩阵乘法。

\[ \begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 0 \\ x_1 & x_1 & 1 \end{bmatrix} \times \begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 0 \\ x_2 & x_2 & 1 \end{bmatrix} \times \begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 0 \\ x_3 & x_3 & 1 \end{bmatrix} \times \begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 0 \\ x_4 & x_4 & 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 \\ 4 & 1 & 0 \\ 4x_1+3x_2+2x_3+x_4 & x_1+x_2+x_3+x_4 & 1 \end{bmatrix} \]

规律应该比较明显,所以我们只需要对于一个节点维护矩阵左下方的三个值即可,常数骤减,根本不要按照别的方法一样考虑标记的转移顺序,这很有道理!

那接下来考虑区间历史最值问题,我们以这道题为例,区间加与区间覆盖不变,但查询的是区间最大值区间历史最大值,自然地我们考虑把矩阵乘法换成 \(\{max,+\}\) 的矩阵乘法。

我们考虑对于每个节点 \(i\),维护 \(\begin{bmatrix} maxn_i & num_i & 0 \end{bmatrix}\),接下来的两种操作可以描述:

  • 区间加操作,\(num_i' = num_i + x\)\(maxn_i' = \max(maxn_i,num_i + x)\)

  • 区间覆盖操作,\(num_i' = x\)\(maxn_i' = \max(maxn_i,x)\)

我们写成矩阵乘法形式,对于区间加:

\[\begin{bmatrix} maxn_i & num_i & 0 \end{bmatrix} \times \begin{bmatrix} 0 & -\infty & -\infty \\ x & x & -\infty \\ -\infty & -\infty & 0 \end{bmatrix} = \begin{bmatrix} maxn_i' & num_i' & 0 \end{bmatrix} \]

对于区间覆盖:

\[\begin{bmatrix} maxn_i & num_i & 0 \end{bmatrix} \times \begin{bmatrix} 0 & -\infty & -\infty \\ -\infty & -\infty & -\infty \\ x & x & 0 \end{bmatrix} = \begin{bmatrix} maxn_i' & num_i' & 0 \end{bmatrix} \]

对于没有操作的区间:

\[\begin{bmatrix} maxn_i & num_i & 0 \end{bmatrix} \times \begin{bmatrix} 0 & -\infty & -\infty \\ -\infty & 0 & -\infty \\ -\infty & -\infty & 0 \end{bmatrix} = \begin{bmatrix} maxn_i' & num_i' & 0 \end{bmatrix} \]

于是就结束了。

posted @ 2025-10-28 14:53  Oken喵~  阅读(12)  评论(1)    收藏  举报