在acm被称为快速幂(平方求幂) 在ctf中也被称为 $montecarlo$
先说幂运算的朴素做法,在数学中,重复连乘的运算叫做乘方,乘方的结果称为 幂
${\displaystyle n}$ 个相同的数 ${\displaystyle b}$ 连续相乘
$$
b^n = \overbrace {b * b * b * ···} ^ {n \ number\ of\ b}
$$
1 | // c++ version |
让我们先来思考一个问题:7的10次方,怎样算比较快?
方法1:上述提到的朴素算法, 来计算 $7 7=49$,$49 7=343$
这样算无疑太慢了,尤其对计算机的CPU而言,每次运算只乘上一个个位数,无疑太屈才了。这时我们想到,也许可以拆分问题。
方法2:先算7的5次方,即$7 7 7 7 7$,再算它的平方,共进行了5次乘法。
方法3:先算7 7得49,则7的5次方为$49 49 * 7$,再算它的平方,共进行了4次乘法。
递归快速幂
刚刚我们用到的,无非是一个二分的思路。我们很自然地可以得到一个递归方程:(虽然我觉得并不自然)
$$
b^n=\begin{cases}b^{n-1}\cdot b,&\text{if } n \text { is odd} \ b^{\frac{n}{2}}\cdot b^{\frac{n}{2}}, &\text{if } n \text { is even but not 0}\ 1,&\text{if } n=0\end{cases}
$$
计算a的n次方,如果n是偶数(不为0),那么就先计算a的n/2次方,然后平方;如果n是奇数,那么就先计算a的n-1次方,再乘上a;
1 | // c++ version |
注意,这个temp变量是必要的,因为如果不把 $a^{\frac{n}{2}}$ 记录下来,直接写成 $qpow(a, n /2)*qpow(a, n /2)$ ,那会计算两次 $a^{\frac{n}{2}}$ ,整个算法就退化为了 $O(n)$
在实际问题中,题目常常会要求对一个大素数取模,这是因为计算结果可能会非常巨大,但是在这里考察高精度又没有必要。这时我们的快速幂也应当进行取模,此时应当注意,原则是步步取模,如果 MOD 较大,还应当开long long
大家知道,递归虽然简洁,但会产生额外的空间开销。我们可以把递归改写为循环,来避免对栈空间的大量占用,也就是非递归快速幂
非递归快速幂
我们换一个角度来引入非递归的快速幂。还是7的10次方,但这次,我们把10写成二进制的形式,也就是 $(1010)_2$
现在我们要计算
$7^{(1010)_2}$ ,可以怎么做?我们很自然地想到可以把它拆分为 $7^{(1000)_2} \cdot 7^{(10)_2}$. 实际上,对于任意的整数,我们都可以把它拆成若干个 $7^{(100…)_2}$ 的形式相乘。而这些$7^{(100…)_2}$,恰好就是 $7^1$ 、$7^2$、$7^4$……我们只需不断把底数平方就可以算出它们。
我们先看代码,再来仔细推敲这个过程:
1 | //非递归快速幂 |
最初ans为1,然后我们一位一位算:
1010的最后一位是0,所以a^1这一位不要。然后1010变为101,a变为a^2。
101的最后一位是1,所以a^2这一位是需要的,乘入ans。101变为10,a再自乘。
10的最后一位是0,跳过,右移,自乘。
然后1的最后一位是1,ans再乘上a^8。循环结束,返回结果。
这里的位运算符,>>是右移,表示把二进制数往右移一位,相当于/2;&是按位与,&1可以理解为取出二进制数的最后一位,相当于 $\mod2==1$。这么一等价,是不是看出了递归和非递归的快速幂的关系了?虽然非递归快速幂因为牵扯到二进制理解起来稍微复杂一点,但基本思路其实和递归快速幂没有太大的出入。
快速幂的拓展
上面所述的都是整数的快速幂,但其实,在算 a^n 时,只要a的数据类型支持乘法且满足结合律,快速幂的算法都是有效的。矩阵、高精度整数,都可以照搬这个思路。下面给出一个模板:
注意,较复杂类型的快速幂的时间复杂度不再是简单的 O(\log n) ,它与底数的乘法的时间复杂度有关。
例如,矩阵快速幂的一个经典应用是求斐波那契数列:
(洛谷P1962) 斐波那契数列
题目背景
大家都知道,斐波那契数列是满足如下性质的一个数列:
$$
F_n = \begin{cases}1& (n \le 2) \ F_{n-1}+F_{n-2}& (n\ge 3) \end{cases}
$$
题目描述
请你求出$F_n \bmod 10^9 + 7$的值。
(以下内容涉及到基本的线性代数知识)
设矩阵
$$
A=\begin{bmatrix}0 &1\ 1 & 1\end{bmatrix}
$$ ,我们有
$$
A\begin{bmatrix}F_n\ F_{n+1}\end{bmatrix} = \begin{bmatrix}F_{n+1}\ F_n+F_{n+1}\end{bmatrix}=\begin{bmatrix}F_{n+1}\ F_{n+2}\end{bmatrix}
$$
,于是 :
$$
\begin{aligned} \begin{bmatrix}F_n\ F_{n+1}\end{bmatrix} &= A\begin{bmatrix}F_{n-1}\ F_n\end{bmatrix}\&=A^2\begin{bmatrix}F_{n-2}\ F_{n-1}\end{bmatrix}\&=…\&=A^{n-1}\begin{bmatrix}F_1\ F_2\end{bmatrix}\&=A^{n-1}\begin{bmatrix}1\ 1\end{bmatrix} \end{aligned}
$$
这样,我们把原来较为复杂的问题转化成了求某个矩阵的幂的问题,这就可以应用快速幂求解了。
1 |
|
![[Pasted image 20221221012506.png]]
引用几乎是复制了这篇文章: https://zhuanlan.zhihu.com/p/95902286