线段树小总结
在维护的信息是区间信息,\(O(logn)\) 的操作时间可以接受时,可以考虑用线段树维护。
懒标记是用来延迟更新的,其含义是:当前的节点信息已经更新,当前节点的儿子需要进行更新(还未更新)。
一般的线段树懒标记较少,可以直接用其实际含义进行维护,比如洛谷上的线段树1和线段树2。
然后我们会发现后面这道题中有两个懒标记,而这两者之间有着一种关系,不能割裂开来看。比如在进行区间乘上 \(k\) 的操作时,不只要修改mul,add也要修改。
这是只有两个懒标记的情况,在[NOIP2022] 比赛这道题中,转换信息之后需要维护7个甚至更多的懒标记,这种情况下直接根据其实际意义来维护懒标记就很麻烦了。
线段树的本质操作是维护矩阵。
维护的信息是一个矩阵,pushup操作相当于矩阵的加法,pushdown和修改相当于矩阵的乘法。
一般的推导步骤:找出题目直接要维护的信息,然后考虑在修改一整个区间(也就是完全包含于被修改区间的节点)时,维护该信息需要维护哪些额外的信息,然后将它们写成一个行向量。
比如要维护区间和,支持区间加和乘:
- 加:\(sum' \leftarrow sum + k \times len\)
- 乘:\(sum' \leftarrow sum \times k\)。
会发现涉及的运算就是整数的加法和整数的乘法,这二者可以用最朴素的矩阵乘法维护起来。
会发现还要额外维护 \(len\)。
维护的信息矩阵就是:
区间加上 \(k\),应该乘上的系数矩阵是(定义乘在后面而不是前面):
同样,区间乘上 \(k\),系数矩阵为:
这个时候,我们线段树的操作已经统一到矩阵的操作了,不管是什么类型的修改,都是乘上一个矩阵,这样合并标记的时候,就不用考虑标记之间复杂的相互作用了,只需要用到矩阵乘法的结合律:
\(A \leftarrow A \times Tag_1 \times Tag_2 = A \times (Tag_1 \times Tag_2)\)。
\(Tag \leftarrow Tag1 \times Tag2\)
所以每次修改就是修改一下信息矩阵,然后修改一下标记矩阵。
但是这样有一个问题:矩阵乘法带了一个常数,虽说这里常数不大,但是复杂度的标记矩阵可以有 \(5 \times 5\)之大,线段树常数也大,而且矩阵乘法不只有最里层循环的计算,每层循环变量自加,判循环条件,都是常数,反正就是标记矩阵太大容易挂……
一个直观的想法是:如果标记矩阵某些位置上的值一直是常数,那我们就不需要维护它了,我们只需要维护矩阵里面的变量。
就好比上面的矩阵,两者都是这种形式(字母代表变量,数字代表常量):
每次更新标记矩阵,都是两个形如上面的矩阵相乘,然后你把4个位置的值都手算一下,发现第二列始终是常量0和1。
于是你就只要维护第一列的值,每个位置上的值都是第一个矩阵对应的行和第二个矩阵对应的列,一一相乘求和。
假设:
每个矩阵维护一个一维数组,相乘的时候按上面的式子算就行了。
更新信息矩阵时:
所以,
实测这样拆开来维护,比直接用实际意义写的跑得还要快。
回顾一下矩阵乘法结合律的证明过程:
小括号里的是矩阵大小。
运用乘法对于加法的分配律:
在枚举 \(i, j\) 的条件下,后面依托可以换元成 \(D_{k,o}\)。
那么就是 \(D\) 的每一个行加起来,然后再把每一列加起来。
同样地,对于后面的 \(A \times (B \times C)\),最后的结果是 \(D\) 的每一个列加起来,再把每一行加起来。
运用加法的交换律和结合律,发现其实这求的都是 \(D\) 所有元素的和,两者是相等的。
事实上,只要有两种运算 \(x\) 和 \(y\),\(y\)有结合律和交换律,\(x\)对\(y\)有分配律,\(x\)就可以作为矩阵乘法中的乘法,\(y\)就可以作为矩阵乘法中的加法(这样子矩阵乘法才有结合律)。
比如可以把整数的加法作为乘法,把整数的max运算作为加法。

浙公网安备 33010602011771号