优化半群结构的线段树信息维护
今天在做区间历史和。感觉给每个标记一个含义实在太抽象了,遂听从白神建议学习矩阵维护信息和优化半群结构。
前置知识:大魔法师,用矩阵维护轮换信息。我们发现区间历史和事实上是对“历史和”变量被“和”变量轮换加法的结果,不知道为什么以前没反应过来和大魔法师有关。
我们用区间加和区间历史和来进行举例。
我们考虑我们做区间加和区间历史和需要的信息。不难想到应该有三个:\(sum\) 表示区间现在的和,\(l\) 表示区间的长度,\(hsum\) 表示区间历史和。把它写成矩阵(或者说列向量)形如:\(\begin{bmatrix}sum \\l \\ hsum\end{bmatrix}\)。
考虑区间加操作,使用一些构造能力构造懒标记即可,很容易得到:
这样就维护好了。注意我们把列向量的信息放到右边,这样每次新信息都可以利用旧信息的每一项转移。
那么也容易构造刷新历史和:
直接用大魔法师一样的办法用矩阵乘法合并懒标记就行。注意新来的标记往旧标记的前面乘才是正确顺序。
但是这样太慢了。
我们发现矩阵里面有很多位置是没用的,乘出来一直都是 \(0\) 或者 \(1\) 这样的定值。我们可以通过刻画标记合并的式子来观察哪些位置是会变化的,哪些位置会是定值。比如我们发现:
我们发现只有三个位置 \(A_{01},A_{20},A_{21}\) 在三个矩阵中是不一样的。其他位置都一模一样。我们把这三个位置在两个矩阵里都用字母替掉可以发现乘出来的矩阵也只有这三个位置会有变化,那么无论怎么乘下去都是这三个位置。
换而言之,一个矩阵的确定只需要这三个元素就够了。所以我们考虑优化半群结构,我们只需要设计一个和矩阵等价的运算可以维护这三个值的变化即可。我们把这三个值放回转移里面发现:
所以我们直接维护 \(\text{tag}=\{a,b,c\}\) 即可。注意新的运算仍然不存在交换律,打标记时需要左乘。
考虑写出矩阵和信息(那个列向量)的乘法来确定标记和信息乘在一起的形态:
容易把最后得到的这个矩阵中每一项赋予意义,但是这样做本身没什么意义了。
upd on 2024.10.30
事实上这个优化半群结构去除矩阵中无意义的项是传递闭包优化。
你发现矩阵乘法形如 \(a_{ij}=b_{ik}\times c_{kj}\)。如果这个东西的图论意义是 \(i\to j\) 是否可达,那么永远都是 \(0\) 的项事实上就是不可达的两个点。所以我们可以跑一遍传递闭包,就能程序化地去掉所有恒为 \(0\) 的项了,不用肉眼发现。
对于去掉恒为其他定值的项目前似乎没有较好的办法。
区间历史最值将矩阵乘法改为 \((\min,+)\) 或 \((\max,+)\) 的广义矩阵乘法即可。注意矩阵单位元的变化。
区间最值操作本质上是转成了区间的两种加减(最大值,其他值都递归),所以把标记和信息拆成两份做(最大值和非最大值)就行了。特别地,如果没有区间加操作,那么我们只需要维护一个区间加减(最值的)就行了。
注意因为需要维护区间加意义下的最大值和次大值,所以可能需要猜一个具有区间加标记含义的标记用来维护最大和次大值。当然直接拆出去维护也可以。
你发现区间求和和以上操作毫无关联,所以可以单独进行维护(也可以猜标记含义合进去)。这样拼在一起就能过线段树 3 了。
写的时候发现的一些小技巧:
- 不一定需要全都拆开两份维护。比如历史最大值,一定是最大值那部分更大(因为只有取 \(\min\)),所以可以不做非最大值的那部分。
- 因为每操作一次就要刷新一次历史最值,所以可以把刷新历史最值的矩阵一来就左乘到加法矩阵上,用这个矩阵对应的半群结构去做。
- 一定要牢记哪个是信息和标记转移出信息,哪个是标记和标记转移出标记。这两种运算的形式截然不同,搞混了巨难调……