zoukankan      html  css  js  c++  java
  • 势能分析

    引入

    在某一类算法流程当中,中间流程十分复杂难以直接分析复杂度。

    为了解决这类问题的复杂度分析,我们引入物理中能量守恒的思想来解决此问题。

    其本质在于将复杂度刻画为做功引起势能变化,而由能量的特性只用管始末状态而不需要管中间的做工情况,恰好能解决开始所提到的中间流程复杂的问题。

    一般地,使用势能分析得到的大多为均摊复杂度。

    简介

    一般模型如下(视情况也可调节):

    令第 (i) 次操作复杂度消耗为 (t_i),第 (i) 个状态为 (S_i),状态 (S) 所对应势能为 (F(S)),那么我们将 (t_i) 描述为如下形式:

    [t_i = c_i + F(S_i) - F(S_{i - 1}) ]

    (可以将 (c_i) 看作常量做功,(F(S_i) - F(S_{i - 1})) 看作势能变化量,(t_i) 为总功)

    那么我们有最终复杂度:

    [sum t_i = sum c_i + F(S_n) - F(S_0) ]

    一般地,我们需要得出 (sum c_i) 的一个上界,以及 (F(S_n) - F(S_0)) 的一个上界,以此确定 (sum t_i) 的一个上界。

    简例

    单调队列复杂度分析

    事实上单调队列复杂度可以简单地分析,但为了熟悉势能分析法我们给出如下分析。

    (S) 为单调队列的一个状态,(F(S) = |S|, t_i) 为第 (i) 次操作消耗复杂度,(c_i) 为第 (i) 次操作入队数量,(S_i) 为第 (i) 次操作结束后(出队结束后)单调队列的状态。

    那么有:

    [t_i = c_i + F(S_{i - 1}) + c_i - F(S_i) ]

    则总复杂度为:

    [sum t_i = 2sum c_i + F(S_0) - F(S_n) = 2n + F(S_0) - F(S_n) le 2n ]

    因此我们也从势能分析的角度得到了单调队列复杂度的证明。

    并查集启发式合并复杂度分析

    约定

    我们记节点 (x) 的子树大小为 (|x|),其势能函数 (varphi(x) = log |x|)


    显然并查集只存在两种操作,于是分两种操作考虑复杂度。

    1. ( t find) 操作

    显然单次复杂度 (mathcal{O}(1)),但由于我们不知道调用次数上限,因此只能将其放缩为势能变化的形式(不带常数)。

    具体地,我们称简介中的第 (i) 个状态为 ( t find) 操作中访问到的第 (i) 个节点 (x) 那么下一个节点即为 (x' = fa_x) 为第 (i + 1) 个状态。

    那么就需要将此常数放缩成 (k(varphi(x') - varphi(x))) 的形式。

    由于启发式合并的特性,我们有:

    [2|x| le |x'| ]

    可知:

    [varphi(x') - varphi(x) = log |x'| - log |x| = log frac{|x'|}{|x|} ge log frac{2|x|}{|x|} = log 2 = 1 ]

    那么有单次复杂度变化量:

    [mathcal{O(1)} le varphi(x') - varphi(x) ]

    因此有 ( t find) 操作总复杂度:

    [sum varphi(x') - varphi(x) = varphi(root) - varphi(x_0) le log n ]

    1. ( t Merge) 操作

    显然 ( t Merge) 操作由两个 ( t find) 操作及一次 (mathcal{O}(1)) 操作构成,由 (1) 的分析可知复杂度为 (mathcal{O(log n)})

    Splay 复杂度分析

    约定

    记小写字母 (x)( t Splay) 上的一个节点,(x')(x) 经过操作变换后对应节点,大写字母 (S) 为一颗 ( t Splay)

    (y)(x)( t Splay) 上的父亲,(z)(y)( t Splay) 上的父亲,(|x|) 为节点 (x)( t Splay) 上子树的大小。

    定义节点 (x) 的势能函数 (varphi(x) = log |x|, mathtt{Splay} S) 的势能函数为 (phi(S) = sumlimits_{x in S} varphi(x))


    下面我们证明 ( t Splay) 单次将节点 (x) 旋到根的复杂度为 (mathcal{O(log n)}) 其他操作均可视为该操作的组合。

    对于 ( t Splay) 双旋的两种情况我们考虑分别将复杂度增量描述为势能变化量。

    同时,由于单次旋转复杂度 (mathcal{O(1)}) 很难描述为势能变化量,因此我们考虑将单次旋转复杂度描述为:(mathcal{O(1)} + Delta phi(S))

    得到两者和的上界再考虑减去 (phi(S)) 总变化量下界即可,由定义可知,总变化量:

    [-n log n le Delta phi(S) le n log n ]

    因此可以将修改后计算出的复杂度加上 (n log n) 即可,不影响总复杂度。

    1. (x, y, z) 三点共线

    有总势能变化量:

    [egin{aligned}Deltaphi(S) &= varphi(x') + varphi(y') + varphi(z') - varphi(x) - varphi(y) - varphi(z)\ &= varphi(y') + varphi(z') - varphi(x) - varphi(y)\end{aligned} ]

    又我们将复杂度描述为 (mathcal{O}(1) + Delta phi(S)),我们并不好衡量常数之和,因此尽量将其放缩为不存在常量的势能变化量。

    显然不能将上界放缩成 (Delta phi(S)) 的形式,因此只能将上界放缩为 (k(varphi(x') - varphi(x)), k) 为常数的形式。

    据此,考虑将复杂度表达式放缩引入 (varphi(x')) 并尽可能化简式子。

    同时,为了方便我们直接将 (mathcal{O}(1)) 看作 (1),最后直接乘该常量即可。

    [1 + Deltaphi(S) le 1 + varphi(x') + varphi(z') - varphi(x) - varphi(y) le 1 + varphi(x') + varphi(z') - 2varphi(x) ]

    考虑借助势能关系放缩 (1),我们有:

    [|x'| = |z'| + |x| ]

    进一步有:

    [varphi(x) + varphi(z') - 2varphi(x') = log frac{|x||z'|}{|x'| ^ 2} le log frac{|x'| ^ 2}{4|x'| ^ 2} = log frac{1}{4} = -2 < -1 ]

    带入原式:

    [egin{aligned} 1 + Delta phi(S) & le 1 + varphi(x') + varphi(z') - 2varphi(x)\ & < varphi(x') + varphi(z') - 2varphi(x) - (varphi(x) + varphi(z') - 2varphi(x'))\ &= 3(varphi(x') - varphi(x)) end{aligned} ]

    旋转结束后,可得总复杂度 (< 3(varphi(root) - varphi(x_0)) < 3log n) 得证。

    1. (x, y, z) 三点不共线

    同样有:

    [egin{aligned}Deltaphi(S) &= varphi(x') + varphi(y') + varphi(z') - varphi(x) - varphi(y) - varphi(z)\ &= varphi(y') + varphi(z') - varphi(x) - varphi(y)\end{aligned} ]

    注意,此时我们所拥有的条件变化,因此不像上一种情况一样放缩。

    具体地,我们有:

    [|y'| + |z'| < |x'| ]

    类似地,可以得到:

    [varphi(y') + varphi(z') - 2varphi(x') < -1 ]

    那么有:

    [egin{aligned} 1 + Delta phi(S) &= 1 + varphi(y') + varphi(z') - varphi(x) - varphi(y)\ & < varphi(y') + varphi(z') - varphi(x) - varphi(y) - (varphi(y') + varphi(z') - 2varphi(x')) \ &= 2varphi(x') - varphi(x) - varphi(y) \ & < 2(varphi(x') - varphi(x)) end{aligned} ]

    旋转结束后,可得总复杂度 (< 2(varphi(root) - varphi(x_0)) < 2log n) 得证。

    由此,我们得到了 ( t Splay) 的复杂度为 (mathcal{O((n + m) log n)})

    LCT 复杂度分析

    显然 ( t LCT) 复杂度只来源于 ( t Access) 操作,而 ( t Access) 操作的复杂度来源又可以分为如下两个部分:

    1. 虚实儿子切链
    2. 实链上 ( t Splay)

    由于这两个部分互不干扰,因此将复杂度分析分开考虑。

    虚实儿子切链复杂度

    首先将树进行重链剖分,称既为原树重边也为 ( t LCT) 中虚边的边成为重虚边,其余类似。

    设置整体势能函数 (phi(S))(mathtt{LCT} : S) 中的 重虚边 数量。

    考虑 ( t Access) 过程中势能函数的变化,有如下两种:

    1. 势能函数增加 (1),当且仅当当前切换链为 轻虚边,原实边为重边。由重链剖分理论,单次 ( t Access) 该情况次数不超过 (log n)
    2. 势能函数减少 (1),当且仅当当前切换链为 重虚边,原实边为轻边。

    (1) 我们有:

    [sum Deltaphi(S) ^ + le m log n ]

    因为有势能函数非负,因此有:

    [sum Deltaphi(S) ^ - le sum Deltaphi(S) ^ + + phi(S_0) le n + m log n ]

    显然总复杂度:

    [sum Deltaphi(S) ^ + + sum Deltaphi(S) ^ - le n + m log n ]

    因此切链复杂度是 (mathcal{O(n + m log n)}) 的。

    实链 Splay 复杂度

    沿用 ( t Splay) 复杂度的分析方式,假设在第 (i) 条实链内部 ( t Splay) 复杂度为 (k(varphi(root_i) - varphi(x_{0_i})))

    显然有:

    [varphi(root_i) < varphi(x_{0_{i + 1}}) ]

    因此总复杂度:

    [sum varphi(x_{0_{i + 1}})- varphi(x_{0_i}) = varphi(root) - varphi(x_{0_1}) le log n ]

    结合两者,有 ( t LCT) 复杂度 (mathcal{O((n + m)log n)})

    基本分析方式

    结合上述四个经典势能分析,我们可以发现势能分析应用时的两种情况:

    1. 复杂度证明并不依赖于势能变化量(证明不依赖于操作对势能的影响),大多数情况下可以被一般方法代替,例如 (1, 2)

    2. 复杂度证明强依赖于势能变化量(证明依赖于操作对势能的影响),一般这类情况是势能分析的专场,例如 (3, 4)

    对于 (1),若需要进行势能分析,往往 只需要 将复杂度描述为势能变化量再考虑始末势能变化量即可,且多数情况下该变化量与操作无关,只于实际问题上界有关。

    对于 (2),需要我们在复杂度描述中引入势能总体变化量,因为这样才方便势能的摊还。

    具体来说,将进行单次操作或可简单分析上界的操作对总势能贡献为 (+),将多次操作或无法分析上界的操作对总势能贡献为 (-)

    这样根据势能函数的非负性,可以得到贡献 (-) 的操作复杂度与贡献 (+) 的操作复杂度之间的关系从而进行求解,例如 ( t LCT) 中切链复杂度的分析。

    还有一种比较复杂度的情况,通过引入整体势能函数无法简单证明,这时我们就需要引入局部势能函数。

    一般对于 后者 描述为势能变化量的形式以放缩开始引入的整体势能函数分析单次操作复杂度。

    简单练习

    二进制加法

    模拟二进制下的加法,从 (0) 开始不断 (+1) 直到 (n),每次的操作为 (mathcal{O}(1)) 修改一个位置的数。

    显然加法过程中 (01) 有别,因此我们将二进制数划分成极大的 (01) 段。

    一种简单的想法是令势能函数为这样的段数,但发现无法用段数描述复杂度。

    于是考虑修改划分方式,我们将一个单独的 (0) 看作一段,极长的 (1) 还是看作一段。

    (varphi(x)) 为数字 (x) 以上述方式划分的段数,分析一下各种情况下 (varphi(x)) 的变化量:

    1. 形如:(cdots 1 cdots 10) 则易知 (varphi(x + 1) = varphi(x) - 1)
    2. 形如:(cdots 00) 易知 (varphi(x + 1) = varphi(x))
    3. 形如:(cdots 001 cdots 1)(最后一段 (1) 的个数为 (k))易知 (varphi(x + 1) = varphi(x) + k - 1)
    4. 形如:(cdots 1 cdots 101 cdots 1)(最后一段 (1) 的个数为 (k))易知 (varphi(x + 1) = varphi(x) + k - 2)

    接下来存在两种证法:

    直接利用势能变化量放缩复杂度

    可知上述四种情况复杂度依次为:(mathcal{O}(1), mathcal{O}(1), mathcal{O}(k + 1), mathcal{O}(k + 1))

    则我们有单次复杂度的一个上界:(varphi(S + 1) - varphi(S) + 3),因此有总复杂度:

    [sumlimits_i ^ {n - 1} varphi(i + 1) - varphi(i) + 3 = varphi(n) - varphi(0) + 3n le 4n ]

    借助势能函数有界性

    观察势能函数的减少量,可知单次至多减少 (1),同时任意时刻我们有界:

    [0 le varphi(x) le log n ]

    假设复杂度增加量之和为:(Delta T ^ + ge 0) 减少量 绝对值 之和为 (Delta T ^ - ge 0),那么有:

    [-log n le Delta T ^ + - Delta T ^ - le log n ]

    由于减少量之和 (Delta T ^ - le n) 因此有总复杂度:

    [le Delta T ^ + + Delta T ^ - + 3n = 5n + log n ]

    并查集路径压缩

    还不会,先咕着......

    并查集路径压缩 + 启发式合并

    也不会,先咕着......

    GO!
  • 相关阅读:
    vue.extend 拓展
    leetcode-166-分数到小数(用余数判断有没有出现小数的循环体)
    leetcode-165-比较版本号
    leetcode-162-寻找峰值
    vector.clear()不能用来清零
    leetcode-209-长度最小的子数组
    leetcode-201-数字范围按位与
    完全多部图的判断(个人思考)
    leetcode-200-岛屿的个数(dfs找所有的连通分量)
    leetcode-151-翻转字符串里的单词
  • 原文地址:https://www.cnblogs.com/Go7338395/p/14771217.html
Copyright © 2011-2022 走看看