线段树小总结

在维护的信息是区间信息,\(O(logn)\) 的操作时间可以接受时,可以考虑用线段树维护。

懒标记是用来延迟更新的,其含义是:当前的节点信息已经更新,当前节点的儿子需要进行更新(还未更新)。

一般的线段树懒标记较少,可以直接用其实际含义进行维护,比如洛谷上的线段树1线段树2

然后我们会发现后面这道题中有两个懒标记,而这两者之间有着一种关系,不能割裂开来看。比如在进行区间乘上 \(k\) 的操作时,不只要修改muladd也要修改。

这是只有两个懒标记的情况,在[NOIP2022] 比赛这道题中,转换信息之后需要维护7个甚至更多的懒标记,这种情况下直接根据其实际意义来维护懒标记就很麻烦了。


线段树的本质操作是维护矩阵。

维护的信息是一个矩阵,pushup操作相当于矩阵的加法,pushdown和修改相当于矩阵的乘法。

一般的推导步骤:找出题目直接要维护的信息,然后考虑在修改一整个区间(也就是完全包含于被修改区间的节点)时,维护该信息需要维护哪些额外的信息,然后将它们写成一个行向量。

比如要维护区间和,支持区间加和乘:

  1. 加:\(sum' \leftarrow sum + k \times len\)
  2. 乘:\(sum' \leftarrow sum \times k\)

会发现涉及的运算就是整数的加法整数的乘法,这二者可以用最朴素的矩阵乘法维护起来。

会发现还要额外维护 \(len\)

维护的信息矩阵就是:

\[\begin{bmatrix} sum & len \\ \end{bmatrix} \]

区间加上 \(k\),应该乘上的系数矩阵是(定义乘在后面而不是前面):

\[\begin{bmatrix} 1 & 0 \\ k & 1 \end{bmatrix} \]

同样,区间乘上 \(k\),系数矩阵为:

\[\begin{bmatrix} k & 0 \\ 0 & 1 \end{bmatrix} \]

这个时候,我们线段树的操作已经统一到矩阵的操作了,不管是什么类型的修改,都是乘上一个矩阵,这样合并标记的时候,就不用考虑标记之间复杂的相互作用了,只需要用到矩阵乘法的结合律:

\(A \leftarrow A \times Tag_1 \times Tag_2 = A \times (Tag_1 \times Tag_2)\)
\(Tag \leftarrow Tag1 \times Tag2\)

所以每次修改就是修改一下信息矩阵,然后修改一下标记矩阵。

但是这样有一个问题:矩阵乘法带了一个常数,虽说这里常数不大,但是复杂度的标记矩阵可以有 \(5 \times 5\)之大,线段树常数也大,而且矩阵乘法不只有最里层循环的计算,每层循环变量自加,判循环条件,都是常数,反正就是标记矩阵太大容易挂……

一个直观的想法是:如果标记矩阵某些位置上的值一直是常数,那我们就不需要维护它了,我们只需要维护矩阵里面的变量

就好比上面的矩阵,两者都是这种形式(字母代表变量,数字代表常量):

\[\begin{bmatrix} a & 0 \\ b & 1 \end{bmatrix} \]

每次更新标记矩阵,都是两个形如上面的矩阵相乘,然后你把4个位置的值都手算一下,发现第二列始终是常量0和1。

于是你就只要维护第一列的值,每个位置上的值都是第一个矩阵对应的行和第二个矩阵对应的列,一一相乘求和。

假设:

\[\begin{bmatrix} a_1 & 0 \\ b_1 & 1 \end{bmatrix} \times \begin{bmatrix} a_2 & 0 \\ b_2 & 1 \end{bmatrix} = \begin{bmatrix} a_3 & 0 \\ b_3 & 1 \end{bmatrix} \]

\[a_3 = a_1 \times a_2 \]

\[b_3=b_1\times a_2 + b_2 \]

每个矩阵维护一个一维数组,相乘的时候按上面的式子算就行了。

更新信息矩阵时:

\[ \begin{bmatrix} sum & len \end{bmatrix} \times \begin{bmatrix} a & 0 \\ b & 1 \end{bmatrix} = \begin{bmatrix} sum \times a + len \times b & len \end{bmatrix} \]

所以,

\[sum \leftarrow sum \times a + len \times b \]

\[len \leftarrow len \]

实测这样拆开来维护,比直接用实际意义写的跑得还要快。

矩阵乘法优化传统线段树


回顾一下矩阵乘法结合律的证明过程:

\[(A(ab) \times B(bc)) \times C(cd) = T(ac) \times C(cd) \]

小括号里的是矩阵大小。

\[\forall i,j,c_{i,j}=\sum^{c}_{k=1}{T_{i,k} \times C_{k,j}} = \sum^{c}_{k=1}({{\sum^{b}_{o=1}{A_{i,o} \times B_{o, k}}}) \times C_{k,j}} \]

运用乘法对于加法的分配律:

\[= \sum^{c}_{k=1}({{\sum^{b}_{o=1}{A_{i,o} \times B_{o, k} \times C_{k,j}}})} \]

在枚举 \(i, j\) 的条件下,后面依托可以换元成 \(D_{k,o}\)

那么就是 \(D\) 的每一个行加起来,然后再把每一列加起来。

同样地,对于后面的 \(A \times (B \times C)\),最后的结果是 \(D\) 的每一个列加起来,再把每一行加起来。

运用加法的交换律和结合律,发现其实这求的都是 \(D\) 所有元素的和,两者是相等的。

事实上,只要有两种运算 \(x\)\(y\)\(y\)有结合律和交换律,\(x\)\(y\)有分配律,\(x\)就可以作为矩阵乘法中的乘法\(y\)就可以作为矩阵乘法中的加法(这样子矩阵乘法才有结合律)。

比如可以把整数的加法作为乘法,把整数的max运算作为加法。

posted @ 2023-10-29 20:28  Zlc晨鑫  阅读(2)  评论(0)    收藏  举报