快速幂

快速幂

一个简单的技术,可以认为是一种小 trick 。
不叫 ksm,可以叫 fastPow,或者叫 binaryPow。
后者的名字为我们解释了这个 trick 的本质的一部分。
这个算法 / 技术 / trick 主要利用了二进制分解。

二进制

可以用二进制表达任意一个十进制整数

\(1_{10} = 1_{2}\)
\(2_{10} = 10_{2}\)
\(3_{10} = 11_{2}\)
\(4_{10} = 100_{2}\)

从低位开始算,第 \(i\) 位如果是 \(1\) 则表示 \(2^𝑖\) 的值:
例如 \(101001_2 = 2^0 + 2^3 + 2^5 = 1 + 8 + 32 = 41_{10}\)

幂次的朴素算法

想要计算 \(𝑏^𝑘\) 的值 ( \(𝑘\) 是整数 ),我们只需要重复 \(𝑘\) 次乘法操作。
假设乘法造成的花费是 \(O(1)\) 的,则这个算法的复杂度为 \(O(𝑘)\)
乘法本身需要大概 \(10\) 个时钟周期,如果 \(k\) 比较大,这个算法表现不佳。
在朴素的工程中可以用这个算法,但是相较之下我们有一个无敌的算法。

一类特殊幂次的求法

能否快速计算 \(𝑏^1,𝑏^2,𝑏^4,𝑏^8,𝑏^{16},……,𝑏^{1073741824}\)\(31\) 项的值?

观察 \(𝑏^2=𝑏^1∗𝑏^1\),实际上有 \(𝑏_𝑖=(𝑏_{𝑖−1} )^2\),这里 \(𝑖\) 表示第 \(𝑖\) 项。

因此只需要递推地做 \(31\) 次乘法就可以求出 \(b^𝑘\) \((k=2^𝑖)\) 的结果。

对于特殊幂次,我们只需要做 \(31\) 次乘法,而非 \(1073741824\) 次。

关于乘法

我们想要求 \(𝑏^𝑥\) 的值。

如果我们将 \(x\) 分解为 \(x≔𝑎_1+𝑎_2+…𝑎_𝑘\) 若干个数的和,
\(𝑏^𝑥\) 可以表示为 $𝑏^{𝑎_1} ∗𝑏^{𝑎_2} ∗…∗𝑏^{𝑎_𝑘} $

这个观察好像启发了我们什么?

对指数进行二进制分解

我们想要求 \(𝑏^𝑥\) 的值。

我们将 \(x\) 分解为 \(x≔2^{𝑎_1} +2^{𝑎_2} +…2^{𝑎_𝑘 }\) 若干个数的和,即二进制表达
\(𝑏^𝑥\) 可以表示为 \(𝑏^{2^{𝑎_1} }∗𝑏^{ 2^{𝑎_2} }∗…∗𝑏^{ 2^{𝑎_𝑘} }\)

我们发现这一定是 \(𝑏^1,𝑏^2,𝑏^4,𝑏^8,𝑏^{16},……,𝑏^{1073741824}\) 的一个子集。
不难发现,此时我们已经将这个算法推广到了任意指数。

流程

image

using i64 = long long;
i64 qpow(i64 a, i64 b, i64 p = 998244353)
{
    i64 res = 1, base = a;
    while (b)
    {
        if(b & 1) res = res * base % p;
        base = base * base % p;
        b >>= 1;
    }
    return res;
}

为什么可以这么做

乘法结合律 \((𝑎∗𝑏)∗𝑐 = 𝑎∗(𝑏∗𝑐)\)
对于 \((b*b*b...*b)\)
\(=𝑏∗(𝑏∗𝑏)∗…∗(𝑏∗𝑏…∗𝑏)\)
\(=𝑏^{2^{𝑎_1} }∗𝑏^{2^{𝑎_2 } }∗…∗𝑏^{2^{𝑎_𝑘 } }\)
这里不需要满足交换律,我们没有使用这个性质。

对于矩阵的乘法

满足结合律 \((𝐴∗𝐵)∗𝐶 = 𝐴∗(𝐵∗𝐶)\),但平凡情况下不满足交换律。

根据前面的分析,对于某个矩阵的 \(𝐵^𝑘\)
矩阵的乘法满足结合律,我们的算法依然适用。

可能觉得这里的说明有点怪,毕竟所有的 \(𝐵\) 都一样,可以说满足交换律。
但我们不需要引入这样强的性质,这点在其他算法里会体现(ST,DDP,多项式)
可以认为快速幂是一类特殊情况,而其他算法存在更加一般的情况。


一些简单作用 - 1

根据费马小定理,\(a^{𝑝−1} \equiv 1 \mod 𝑝\),其中 \(𝑝\) 是一个质数。
两侧同时乘一个 \(a^{−1}\) ,得到 \(a^{𝑝−2} \equiv a^{−1} \mod 𝑝\)

这给出了模 \(𝑝\) 意义下 \(𝑎\) 的逆元 \(a^{−1}\) 的一种计算方法,计算 \(a^{𝑝−2}\) 即可。
常用的质数模数有 \(998244353\)\(10^9+7\) 等,可以用 qpow 加速运算。

一些简单作用 - 2

求一个 \(𝑛\) 项的等比数列的和,公式是 \(a_1 \frac{ (1−𝑞^𝑛)} {(1−𝑞)}\)
不难发现,分子是可以用快速幂的。

如果在模质数意义下,分母总是 \(a^{−1}\) 的形式。
依然可以使用快速幂求解,转化为乘法运算。

一些简单作用 - 3

求斐波那契数列的第 𝑛 项,这里让 2 是第一项

\[\begin{array}{cccc} A = \begin{pmatrix} 1 & 1 \\ \end{pmatrix} \quad B = \begin{pmatrix} 1 & 1 \\ 1 & 0 \\ \end{pmatrix} \\ 𝐹_𝑛=𝐴∗𝐵^𝑛 \end{array} \]

斐波那契的本质是一种线性运算,因此可以应用矩阵乘法
在这个基础上可以用快速幂加速,可以认为做到了 \(𝑂(\log 𝑛)\) / 作弊地

一些简单作用 - 4

\(q\) 次询问,每次询问给出 \(x, 𝑀\) ,回答长度为 \(x\)\(111...111 \mod 𝑀\) 的值

\(Sol1:\) 不难发现是 \(1+10+100…\) 是一个等比数列求和
根据之前的说法,我们可以在 \(𝑂(\log ⁡𝑥 )\) 时间内解决

\(Sol2:\) 递推地看,相当于每次乘 \(10\)\(1\),重复 \(𝑥\)
既然是一系列线性组合,可以计算下式的结果

\[\begin{array}{cccc} \begin{pmatrix} 0 & 1 \\ \end{pmatrix} * \begin{pmatrix} 10 & 0 \\ 1 & 1 \\ \end{pmatrix}^x \end{array} \]

根据之前的说法,我们可以在 \(𝑂(\log ⁡𝑥 )\) 时间内解决

小结

快速幂是一种利用了二进制分解、递推、结合律的信息学 trick / 技术 / 算法

我们可以递推地得知特殊幂次 \(b^{2^𝑘 }\) 的值,这样的点值共有 \(𝑂(\log ⁡𝑥 )\)
接下来利用结合律,将 \(b^𝑥\) 的指数分解为 \(𝑂(\log ⁡𝑥 )\) 个特殊幂次的合并

这样,我们就在 \(𝑂(\log ⁡𝑥 )\) 的时间内求出了 \(b^𝑥\) 的值。
相信大家对二进制分解,有了一些新的看法和感想。

只要某种运算对信息具有结合率,就可以应用这种算法。

可以预见,由于信息本身提供的功能足够强大
E.g. 数字乘法,矩阵乘法,集合交并,多项式

算法能帮助加快解决一系列复杂问题(的一部分)
E.g. 组合计数,离散数学(图论) ……

posted @ 2024-09-22 22:57  Aurora5090  阅读(33)  评论(0)    收藏  举报

再次右键以切换宽度