关于递归式复杂度求解的一些想法。

虽然说具体数学有一整章讲渐进式,但鉴于学这个性价比太低了,基本也用不到,所以很多有关渐进式的实用知识会在本文总结。

接下来进入正题。

渐进式

很多时候,我们并不能很好的得知一个式子的封闭形式,但是对于这个式子知道他的增长速度也是极好的。这里用来表示这种渐进的符号最常用的符号有两种: \(O\) 和 \(\Theta\) 。在算法竞赛,一般直接使用 \(O\) 即可,但是在实际数学使用中两种符号其实是有区别的: \(O\) 表示的是 渐进上界(asymptotically upper bound) ,表示式子的量级不会超过这个数;而 \(\Theta\) 表示的是 渐近紧确界(asymptotically tight bound) ,也就是说记号 \(O\) 并不含式子渐进下界的信息,不过在关心时间复杂度上界的算法竞赛中下界并不重要,因此直接用 \(O\) 是没有问题的。当然还有记号 \(\Omega\) 表示 渐进下界(asymptotically lower bound) ,只不过它不太常用,我们就不对其多加讨论。

计算渐进式的方法有很多,下面会一一介绍。

主定理及其局限性

这是本文十分重要的一个点。这里先介绍一下主定理,这是一个非常好的求解递归式渐进的方法(证明略):

Theorem:

\[\begin{align*}

& 设\ a\geq1,b\geq1\ ,\ f(n)\ 为一定义在非负整数上的函数,\\

& T(n)=aT(\frac nb)+f(n)\ (当\ \frac nb\ 不为整数时代表\ \lfloor\frac nb\rfloor\ 或\ \lceil\frac nb\rceil\ ),则有:\\

\\

& 1.若存在\ \varepsilon>0\ ,\ 使得\ f(n)=O\left(n^{\log_ba-\varepsilon}\right)\ ,则\ T(n)=\Theta\left(n^{\log_ba}\right) \\

& 2.若存在\ k\geq0\ ,\ 使得\ f(n)=O\left(n^{\log_ba}\lg^kn\right)\ ,则\ T(n)=\Theta\left(n^{\log_ba}\lg^{k+1}n\right) \\

& 3.若存在\ \varepsilon>0\ ,\ 使得\ f(n)=O\left(n^{\log_ba+\varepsilon}\right)\ ,且存在\ 0N\ 时,有\ af\left(\frac nb\right)\leq cf(n)\ ,则\ T(n)=\Theta(f(n))

\end{align*}

\]

主定理的适用范围是非常广的,但是它对下面这个式子束手无策:

\[\begin{flalign}

\label{1}

T(n) = 4\sqrt nT(\sqrt n)+n

\end{flalign}

\]

发现主定理用不了,因为子问题里的 \(\sqrt n = \frac{n}{\sqrt n}\) 得到 \(b=\sqrt n\) ,这并不是一个常数,此时主定理就用不了了。

除了上式外,看到下面这个递归式:

\[\begin{flalign}

\label{2}

T(n) = T(\frac n2)+T(\frac n4)+T(\frac n8)+n

\end{flalign}

\]

这个式子的子问题并不是平均划分的,因此也就不能用主定理。

我们再看一个式子:

\[\begin{flalign}

\label{3}

T(n)=4T(\frac n4)+\frac n{\lg n}

\end{flalign}

\]

此时该式子不符合主定理的三种情况,也是不能用主定理的。

不那么暴力的暴力

对于不能用主定理解决的递归式,《算法导论》中推荐使用递归树+数学归纳法解决,但是这样工作量过于巨大了,会显得很蠢,还不如乱猜再证明来的快,此时就十分的难受,不知道怎么整。

为了让我们能更简洁与优美的暴力,采用更好的方法是必要的。我们尝试在递归树的基础上进行优化。就像记忆化搜索可以优化深度优先搜索一样,我们可以对每一个可能出现的子问题考虑它对答案的贡献。

拿 \((1)\) 式举例,我们无需列出一整棵递归树,只需对每个子问题统计贡献即可。用 \(w(x)\) 表示子问题 \(T(x)\) 的贡献,就可以有下面的表:

\(x\)

\(w(x)\)

\(n\)

\(n\)

\(\sqrt n\)

\(4\sqrt n \times \sqrt n = 4n\)

\(\sqrt {\sqrt n}\)

\((4\sqrt {\sqrt n})(4\sqrt n )(\sqrt {\sqrt n}) = 16n\)

相信对数字敏感的人无需列表即可感性地证明规律,即使不太敏感,表的前三项也足够找到规律:

\[w(n^{0.5^x}) = 4^xn

\]

那么设 \(n=2^k\) 那么层数就应该是:

\[\log_2 k = \log_2 \log_2n

\]

总的复杂度就会是:

\[\sum\limits_{i=0}^{\log_2 \log_2n}4^in

\]

\[\sum\limits_{i=0}^{\log_2 \log_2n}4^i \sim 4^{\log_2 \log_2n}

\]

所以答案为:

\[\begin{align*}

4^{\log_2 \log_2n}n &= 2^{2\log_2 \log_2n}n\\

&= (2^{\log_2 \log_2n})^2n\\

&= \mathcal O(n(\log^2n))

\end{align*}

\]

如果具有较好的和式计算能力的话,这种做法也是可以得出和主定理一样的结果的。下面是 Codeforces Round 960 F题 的一种错误做法最劣时间复杂度分析,它是一个基于递归的分治算法。递归树这一步就省略了,得出的式子是这样的:

\[\begin{align*}

T(n) &= \underbrace{\log_2n+2\log_2\left(\frac n2\right)+4\log_2\left(\frac n4\right)+\cdots}_{\log_2n项} \\

&= \log_2n+\sum_{k=1}^{\log_2n}2^k\log_2\left(\frac n{2^k}\right)

\end{align*}

\]

我们把后面的和式拎出来处理:

\[\begin{align*}

\sum_{k=1}^{\log_2n}2^k\log_2\left(\frac n{2^k}\right) &= \sum_{k=1}^{\log_2n}2^k(\log_2n-k)\\

&= \log_2n\sum_{k=1}^{\log_2n}2^k-\sum_{k=1}^{\log_2n}k2^k

\end{align*}

\]

具体数学一书中有对和式后面一个和式的化简,这里不赘述化简过程: \(\sum_{k=1}^{m}k2^k = (m-1)2^{m+1}+2\) ,所以:

\[\begin{align*}

\log_2n\sum_{k=1}^{\log_2n}2^k-\sum_{k=1}^{\log_2n}k2^k &= \log_2n(2^{\log_2n+1}-2)-(\log_2n-1)2^{\log_2n+1}+2 \\

&= \log_2n(2n-2)-(\log_2n-1)2n+2 \\

&= 2n\log_2n-2\log_2n-2n\log_2n+2n+2 \\

&= 2n-2\log_2n+2

\end{align*}

\]

带回到原式就有 \(2n-\log_2n+2\) ,即单次询问时间复杂度是 \(\mathcal O(n)\) 的,并不能通过。

不妨再来一组。我们来解决式 \((2)\) ,而这甚至表都可以不列出来。不难发现层数最为 \(\log_2n\) ,而层贡献为 \(层值 \times 层出现次数\) ,假设当前层为 \(n\frac 1{2^k}\) ,相当于一次能走一步、两步或三步,走 \(k\) 步的方案数, \(OGF\) 搞起,但是没搞出来(?),但是可以从组合的角度近似地看作把 \(k\) 个数分成三段,那么这个系数就应该是 \(\mathcal O(k^2)\) 的,那么答案就是:

\[\sum\limits_{i=0}^{\log_2n}i^2\frac 1{2^i}n = \mathcal O(n)

\]

最后我们来搞定式 \((3)\) ,依然是把层数算出来,是 \(\log_4n\) ,那么答案就是:

\[\sum\limits_{i=0}^{log_4n}4^i\frac {\frac n{4^i}}{\lg \frac n{4^i}} = \sum\limits_{i=0}^{log_4n} \frac {n}{\lg \frac n{4^i}}

\]

然后发现式子并不能化简到封闭形式,此时我们就需要下面的定理。

Akra-Bazzi定理

Theorem:

\[\begin{array}{l}

设\ g(x)\ 为一非负函数,\ T(x)=

\left\{\begin{array}{ll}

\Theta(1) &,1\leq x\leq X_0 \\

\sum\limits_{i=1}^ka_iT(b_ix)+g(x) &,n>X_0

\end{array}\right.\\

(其中\ k\geq 1,a_i>0,0\frac 1{b_i}\ 且\ X_0>\frac 1{1-b_i})\\

若\ g(x)\ 满足多项式增长条件,\ p\ 为方程\ \sum_{i=1}^ka_ib_i^p=1\ 的实数解,则\ T(x)=\Theta\left(x^p\left(1+\int_1^x\frac{g(u)}{u^{p+1}}du\right)\right)

\end{array}

\]

其中的多项式增长条件可以简易地理解为 \(\exists c>0,|g'(x)|\in \mathcal O(x^c)\) 。

开了之后我们再回到式 \((3)\) , \(p\) 显然为 \(1\) ,那么代入定理得到:

\[\begin{array}{ll}

T(n) &= \Theta\left(n\left(1+\int^n_1\frac {\frac x{\lg x}}{x^2}dx\right)\right)\\

&= \Theta\left(n\left(1+\int^n_1\frac {1}{x\lg x}dx\right)\right)\\

&= \Theta(n+(1+\lg\lg n))\\

&= \Theta(n\lg\lg n)

\end{array}

\]

Akra-Bazzi 定理原论文里的证明用了一种称为 阶变换(Order Transform) 的技巧,还涉及泛函分析,这些笔者还在学习中。当然粗暴一点的证明有数学归纳法,这里受限于本人能力不足,不做证明。

总结

总结一下,对于一般的递归式,可通过主定理求渐进解。而对于 \(b\) 不是常数的情形,可以分层分析贡献。最后对于 \(g(n)\) 满足多项式增长的式子,可以尝试使用 Akra-Bazzi 定理。