关于势能分析
可能有不少不严谨之处,太菜了请谅解。
之前对于 \(\text{splay}\) 的复杂度一直不是很懂,今天进行了一个势能分析的学习。
势能分析,就是借助势能函数,将中间过程用势能函数来刻画以得到发杂度的一个上界,这样分析出来的一般是均摊复杂度。
例如,第 \(i\) 此操作的代价是 \(c_i\),那么设第 \(i\) 次操作的复杂度 \(C_i\) 是:
即
若 \(\sum{c_i}\) 和 $ \phi(n) - \phi(0) $ 存在一个上界,那么我们就算出了一个复杂度的上界。
一些例子:
1. 栈
维护一个栈,支持 \(\mathrm{push}\),\(\mathrm{pop}\),\(\mathrm{multipop}\) 三种操作,\(\mathrm{push}\) 和单次 \(\mathrm{pop}\) 的复杂度都是 \(O(1)\),进行 \(n\) 次操作,分析其复杂度。
直觉来看,复杂度是 \(O(n)\) 的,怎么证明呢?现定义势能函数 \(\phi(i)\) 为第 \(i\) 次操作后栈内元素个数。那么:
- \(\mathrm{push}\):\(C_i = c_i + \phi(i) - \phi(i-1) = 1 + 1 =2\)
- \(\mathrm{pop}\):\(C_i = c_i + \phi(i) - \phi(i-1) = 1 - 1 =0\)
- \(\mathrm{multipop(k)}\):\(C_i = c_i + \phi(i) - \phi(i-1) = k - k =0\)
\(n\) 次操作,因此复杂度 \(O(n)\)。
2. 二进制加法
一个二进制数,从 \(1\) 加到 \(n\)。证明时间复杂度。
定义 \(\phi(i)\) 为第 \(i\) 次操作后 \(1\) 的个数。
一次加 \(1\) 相当于 \(k\) 次 \(1 \rightarrow 0\) 和一次 \(0 \rightarrow 1\),则:
一次操作均摊 \(O(1)\),总的就是 \(O(\log n)\)。
2.5 一些理解
- 势能分析的作用,就在于通过设计函数,把时间长的操作拼到时间短的上面。
- 由上一条的启发,我们得到一个设计函数的思路:执行时间长的操作时,势能一般是降低的,时间短的操作时反之。
- \(C_i = c_i + \phi(i) - \phi(i-1)\),中间的代价和势能之间的相加有什么意义吗?其实这没有任何实际意义,只是我们利用其来进行一个平衡。
3. Splay
现在要设计一个势能函数,根据我们的直觉,势能函数理应与 \(\mathtt{Splay}\) 的平衡有关。
下面记 \(\left|x\right|\) 为 \(x\) 子树的大小。
若一 \(n\) 个点的 \(\mathtt{Splay}\) 进行了 \(m\) 次 \(\mathtt{Splay}\) 操作。
记 \(\phi(i) = \log(size(i))\),整棵 \(\mathtt{Splay}\) 的势能函数 \(\Phi(T) = \sum{\phi(x)}\)。
则一次旋转的代价为 \(C_i = O(1) + \Delta\Phi(T)\)。
设要旋转的节点为 \(x\),其父亲和父亲的父亲分别为 \(y\) 和 \(z\),那么:
- 进行一次 \(\text{zig}\) 操作时:
- 进行 \(\text{zig-zag}\) 操作时:
然后进行一个神秘的放缩:
因为旋转后
因此
所以
- 进行 \(\text{zig-zig}\) 操作时:
又因为
因此
代换得:
- 如果 \(\text{zig-zig}\) 操作两次都转的是 \(x\) 的话,\((1)\) 式就不在成立,我们也很难通过放缩把那个 \(O(1)\) 消掉,那复杂度就不对了。
综上,除了最后一次旋转外,其余的常数都被我们消掉了。因此一次 \(Splay\) 复杂度为 \(O(1) + 3(\phi(root) - \phi(x_0)) < 3\log n\)。
\(m\) 次操作,总复杂度为 \(m\log n + \Delta \Phi(T) = (m+n)\log n\)。
4. LCT
\(\mathtt{LCT}\) 的复杂度只来源于 \(\text{Access}\) 操作,因为单独的 \(\text{Splay}\) 操作是严格小于 \(\text{Access}\) 中的多次 \(\text{Splay}\) 的。(应该是的吧?)
沿用对 \(\text{Splay}\) 的势能分析,对于 \(\text{Access}\) 中的第 \(i\) 次 \(\text{Splay}\):
因为 \(\phi(root_i)\) 一定小于 \(\phi(x_{i+1})\),因此:
其中 \(k\) 是换链次数,现在只需要证明 \(k\) 的复杂度就好了。证明过程非常神奇:
对原树重链剖分,现在边有了轻重之分。
记势能函数 \(\Phi(T)\) 为整棵树中重虚边的数量。那么在一次 \(\text{Access}\) 操作中:
- 重实边切成轻实边,\(\Phi(T)\) 增加 \(1\)。
- 轻实边切成重实边,\(\Phi(T)\) 减少 \(1\)。
注意到一个点到根的路径中最多经过 \(\log n\) 条轻边,因此 \(\Phi(T)\) 最多增加 \(n \log n\) 次,相应地切边最多也只有 \(n\log n\) 级别。
这样我们就用势能分析证明了 \(\mathtt{LCT}\) 的复杂度。
(其实我们也顺带证明了假如说用 \(\mathtt{fhq\; Treap}\) 来维护的话复杂度为什么是 \(n \log^2 n\) 的,因为一棵 \(\mathtt{fhq\; Treap}\) 只保证了深度为 \(\log n\),并不能像 \(\mathtt{Splay}\) 一样看成一个整体来看待。

浙公网安备 33010602011771号