深入理解非递归快速幂
Posted santiego
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解非递归快速幂相关的知识,希望对你有一定的参考价值。
深入理解非递归快速幂
按
网络上很多博客都只有板子,并没有严谨详细探讨其思想。本篇文章将从二进制角度来深入理解非递归快速幂
原理
先举个栗子,求 (3^{15}) 。对于 (3^{15}) 我们可以通过数学幂的运算法则得到下面的式子
[ 3^{17}=3^{2^{4}+2^{0}}=3^{2^{4}}*3^{2^{0}} ]
[
3^{2^{i}}=3^{2^{i-1}}*3^{2^{i-1}}
]
我们会发现将其指数化为以2为底数的数之和后,计算 (3^{15}) 时,仅需算出 (3^{2^{4}}) 和 (3^{2^{0}}) 便可求得 (3^{15}) 的值,相比于遍历指数次,每次乘以底数的传统做法,效率更高。而我们又会发现 (3^{2^{i}}) 是由 (3^{2^{i-1}}) 转换过来的,所以,我们只需求出之和构成指数的2为底数的各个数(即问是哪些以2为底数的数累加起来为指数的),便可求解。
[
17=2^{4}+2^{0}
]
而后,我们再通过观察发现,上面这个式子中的4和0其实就是17的二进制上不为0的二进制位
(废话,二进制本身不就是将任意十进制数拆解为以2为底数的数之和的过程吗)
找出之和构成指数的以2为底数的各个数后,我们只需要从 (3^{2^{0}}) 开始,每次平方本身,推出第i位的 (3^{2^{i}}) ,并根据上面的分解规则(规律),逐位判断二进制从而解决最后的问题从而最终求出快读幂。
总而言之,我觉得快速幂算法其实是利用二进制将数分解为很多个递归平方(词穷了),每次都以平方级增长,从而实现快速求幂。
实现
先上个非递归快速幂板子
LL pow_mod(LL a, LL b, LL mo){
LL ret = 1;
while(b){
if(b & 1) ret = (ret * a) % mo;
a = (a * a) % mo;
b >>= 1;
}
return ret;
}
首先需要明白的是,b&1
在二进制中可以理解为取b的二进制最后一位,如果b&1
返回1(即true)则末尾为1,反之为0(所以根据这个性质也可以很快地判断一个数是否为奇数),所以在这里只有当b的二进制最后一位为1时,才执行其后语句ret = (ret * a) % mo;
;另外b >>= 1
是表示将b右移1位,即将b的二进制整体向右移动一格。
既然理解了原理以及实现要点,希望读者尽可能自己琢磨一下这个程序,可以模拟感受一下,因为那样来得更快
在这个程序中,每一次,无论当前二进制最后一位是否为1,a
都要平方一下(因为 (3^{2^{i}}=3^{2^{i-1}}*3^{2^{i-1}}) ,前一个值会影响后一个值,所以必须维护),然后将当前二进制右移一位,去掉已经处理过的二进制数位以处理下一位,但当当前二进制最后一位为1时,根据之前的规律,需将a
加入到答案中,最终当所有二进制数位都被处理掉后,b自然变为0,while循环结束。
以上。
参考
以上是关于深入理解非递归快速幂的主要内容,如果未能解决你的问题,请参考以下文章