[复习资料]有关二元运算结合律的一些遐想
有关二元运算结合律的一些遐想
为什么突然想写这个?因为今天写一道线段树题目的时候发现自己不会懒标记的运用,然后就推了好久,推出来之后就开始乱想,于是就有了这篇博客。
由于我数学不太好,所以表述的可能会有点不标准,不过看得懂就行了。
写完后不知道自己到底写得是啥,不过先挂上来吧,可能会有点用处?(大雾
结合律是啥?
在数学中,结合律(associative laws)是二元运算可以有的一个性质,意指在一个包含有二个以上的可结合运算子的表示式,只要算子的位置没有改变,其运算的顺序就不会对运算出来的值有影响。——摘自百度百科
设这种运算的记号为 \(\times\) ,这种运算的元素包括 \(a,b,c\) ,简单来说,就是 \((a\times b)\times c=a\times (b\times c)\) 。
线段树上面的 pushdown
用 \(A,B,C,\dots\) 表示线段树上面每个节点维护的值,用 \(a,b,c,\dots\) 表示线段树上面每个节点的懒标记,由于懒标记和值的类型可能不同,我们把值和懒标记间的运算记为 \(\times\) ,懒标记和懒标记间的运算记为 \(*\) 。
定义懒标记的时候我们一般是要让所有的修改都可以使用懒标记来表示,在此同时我们就已经定义完了值和懒标记间的运算,现在需要解决的问题就是如何定义懒标记间的运算。
我们考虑懒标记的实现原理:在任意一个操作结束之后,如果线段树维护的某一个节点的值为 \(A\) ,从这个节点到根的路径上的懒标记依次为 \(a,b,c,\dots\) ,那么这个节点在这次操作后实际维护的值应该是 \(A\times a\times b\times c\dots\) ,在我们进行 pushdown 节点 \(x\) 的操作的时候, \(x\) 到根这条路径上的除 \(x\) 外的所有节点应该都已经进行完 pushdown 操作了,此时 \(x\) 节点上面的懒标记对 \(x\) 子树内所有节点的影响都是最后进行的,在 pushdown \(x\) 操作进行完后, \(x\) 节点上面的懒标记变成了单位元了,也可以认为这次操作不存在了,也就是说,我们合并了应该进行的最后两次操作。
由此我们可以得出,如果 \(A\times a\times b=A\times c\) ,那么 \(a*b=c\) ,请注意,这里必须要求 \(c\) 和 \(A\) 无关而仅和 \(a,b\) 有关,即对于任意的 \(A\) ,都满足 \(A\times a\times b=A\times c\) ,于是这样我们便定义了懒标记间的运算。
照这样来说,线段树的懒标记 pushdown 和结合律貌似是没有关系的,但是平衡树 pushdown 的时候恐怕就和结合律有关了。
Splay 上面的 pushdown
一般情况下,我们在 Splay 里面所写的平衡树, pushdown 操作都是在 rotate 里面进行的(这点和 LCT 不同,当然每个人的写法可能不一样),在进行 pushdown 节点 \(x\) 的操作的时候,并没有保证 \(x\) 到根节点路径上面的每一个节点都 pushdown 完了,所以此时我们合并的相当于是中间进行的两次操作,而只有在懒标记间的运算满足结合律的时候我们才能保证这样做没有问题。
假如懒标记间的运算没有满足结合律,那么我们在写 Splay 的时候就要像写 LCT 一样,先把根到当前节点的路径上面的所有懒标记下放,再进行 Splay 操作。
快速幂
快速幂,可以用来快速计算某一个整数的连乘,但是并不止于连乘,我们可以用它来快速计算其他运算的“连乘”,但是可以使用快速幂来做“连乘”的时候对于运算本身的性质有什么要求呢?
考虑快速幂的实现原理(以下是用递归式的快速幂来解释的,其实非递归式的快速幂本质上没有什么区别):
可以看出,我们在进行快速幂的时候人为地在中间添加了括号,也就是改变了运算顺序,而只有在运算满足结合律的时候才可以这样改变运算顺序。
矩阵可以使用快速幂,我们 ddp 用到的广义矩阵也可以使用快速幂,因为它们间的运算都有结合律,一切满足结合律的运算都可以快速幂。