都知道算某个数的幂须要线性的复杂度,为了优化复杂度,就出现了所谓的快速幂。算法
快速幂的代码很短,可是要原理须要一点心思。学习
首先,咱们知道, 优化
a^b = a^c * a^d (c+d=b)spa
那么,不就能够经过 a^b = a^b1 * a^b2 * a^b3... * a^bn (b1 + b2...+bn = b) 来快速得到a^b吗?这个方法的优越性在于,若是能够线性的求出a^b1~a^bn,不就是很快的算法吗?code
由于a^b=a^c*a^d,c+d=b这条原理,咱们的目标是找到广泛知足 b = b1 + b2 ... + bn的规律。因此,咱们必需要找出一个统一的方法来肯定b对应的b1~bn分别是多少。blog
若是各位知道进制转换的原理,就能够知道:一个n进制的数转为十进制等于 求和( i = 0~n-1 ) n ^ i * 第 i 位的数字。博客
例如,十进制数10的二进制数1010能够表示为: 2^0 * 0 + 2^1 * 1 + 2^2 * 0 + 2^3 * 1 (这里挺绕的,为了方便理解和验证,2^3*1意思是 2进制,第3位,二进制数字1010中第三位是1)数学
仔细一想,这不就是咱们要的"肯定b对应的b1~bn分别是多少"吗?这也是快速幂的原理所在,将一个质数分解为许多能够线性一个个求出的2的次方的幂。class
我再次重申a^b=a^c*a^d,c+d=b这条原理,不能不搞清楚乘法和加法的关系,咱们以前获得的加法规律其实是应用于c+d=b这里的,最后的计算仍是要用乘法。变量
以前我一直在说,这个方法或者说规律的优越性在于咱们能够线性的求出相加的每个指数。
例如,我知道十进制数10,也就是二进制数1010,我就能够在线性时间复杂度里获得 2^0 * 0 、 2^1 * 1 、 2^2 * 0 、 2^3 * 1。
说了这么多废话,目的在于接下来的这一条原理。
( a^(2^0) )^2 = a^(2^1) 你能够亲自验证一下这条神奇的性质。不只是对0+1 = 1有效,你能够把0换成任何一个数,把1换成那个数+1,看看还会不会生效。
对于每一个数k,概括一下,就是 (a^(2^k) )^2 = a^(2^(k+1) ) 。其实本身稍微一想,就明白是怎么回事了。
typedef long long ll;
ll poww(ll a, ll b)//a^b { ll re = 1; while(b != 0) { if(b & 1) re *= a; a = a * a; b >>= 1; } return re; }
解释一下代码。
b & 1表明着咱们以前的判断"为了避免让它“滥竽充数”地也来分一杯羹,咱们使用&运算符,判断这个二进制最后一位是否为0"。为何要判断“最后一位”,由于咱们在判断完指数的二进制的某一位后,那一位再也不有用,
因此咱们使用 b >>= 1也就是位运算符「右移」来消除使用过的那一位。因为咱们不想计算当前是哪一位,而且但愿代码尽量的简便高效,咱们只能不计当前是多少位,
用以前说过的利用平方来求下一个b1~bn中的一个。(b=Σ(i=1~n) bi,Σ为求和,求b1+.2+...bn,我有点啰嗦,但能让更多人看懂)。
因此,为了简洁高效地完成任务,实际上咱们把原来的 一个n进制的数转为十进制等于 "求和( i = 0~n-1 ) n ^ i * 第 i 位的数字” 变成了 “ 求和( i = 0~n-1 ) n ^ i ”。
例如,十进制数10的二进制数1010按照咱们原来的方式是: 2^0 * 0 + 2^1 * 1 + 2^2 * 0 + 2^3 * 1,而在代码里是 2^0 + 2^1 + 2^2 + 2^3
这就不可避免的形成了在某个数的二进制的某一位是0而形成本该是0的指数被咱们计算成了别的数。因此,咱们必定要在一开始加上二进制下最后一位是否为0的条件,若是不是0那么就能够把当前获得的结果乘到咱们的最终结果变量上,若是是0则不能乘到最终结果变量上,可是a依然要平方,不能由于这一位数字的结果被忽略而不计下一位数字的结果应当按照咱们以前的方法线性求出本当乘上的数字。(例如,按照咱们以前1010的例子,2^0 * 0被咱们忽略,可是若是上一位数字的结果被忽略就不考虑下一位的话,这一位的指数就是2^0*1了,与咱们期待的结果不符。)
说了这么多,发现本身想说的其实能够精炼一下,把本身的思考过程部分隐去。可是转念一想,对于第一次据说线性筛的OIer或者别的学习者须要详细的描述,而我本身也不能保证一直记住快速幂的原理,权当整理了。
若是本篇博客有差错或者不恰当之处,请多多指正。