快速幂算法(二分思想减少连乘次数)
快速幂是什么
如果要我们求某个数的幂 \(a^{n}\) ,我们的朴素算法,也就是最最简单的做法,自然是先设一个表示最终结果的变量ans,初值为1,然后for循环n次,每次都用a去乘ans啦,最后ans被乘完之后就是我们的幂的结果。但是如果我们这个数很大的话,那么就要进行很多次循环,这样速度是很慢的,所以我们会想要用一种算法来改进,这就叫做快速幂。
快速幂的思路
我们本来的朴素算法中,如上文所说,就需要设一个变量ans,初值为1,用它表示最后的得数,在过程中要乘很多次底数嘛,最后整个幂计算函数就返回该变量。那么在快速幂算法中,我们同样需要首先设一个ans,初值为1。
对于任意一个幂运算 \(a^{b}\) ,我们的基本思路其实是二分的思想。如果b是偶数,且b/2=c,那么我们可以将 \(a^{b}\) 转化成 \((a^{2})^{c}\) ,也就是指数缩小一半而底数变为原来的两次方。当b很大的时候,这样做就减少了很多次循环(假设b=10000那么就减少了5000次循环)。
但是如果b很大,然后b / 2=c,c也很大,那这个时候还得接着往下除以二,但如果c变成了奇数(或者另一种情况,本来最开始的b就已经是奇数了)这时候应该怎么办呢?
现在要对 \(a^{c}\) 进行处理,并且c是奇数,那么我们可以在 c 中取一个1出来,也就是说将 \(a^{c}\) 化为 \(a^{c-1} \times a^{1}\) 。这样的话c-1又可以对半分了,然后整个式子中就由左边的 \(a^{c-1}\) 和右边的 \(a^{1}\) 组成,右边部分已经是1次方了不用再优化,左边的a的偶数次方可以再进行优化。
既然右边部分已经是a的1次方了,没法再优化了,那么每次我们拆出一个a的1次方时,我们就可以直接把它存进上文提到的这个ans中,即ans *= 这个a的一次方。
那么这样二分下去,底数会一直变大,而指数会一直变小,到最后指数肯定会缩小到剩余的部分变为(a的某次方)^2那么它再分解就是两个1次方了,根据我们上面的规则,一旦拆出了指数为1 的乘项,就直接把它乘进ans中。那么最后当指数变为零的时候,循环也就结束了,也就得到了我们所要的结果了。
那么我们可以来看一下上述思想如何实现,计算 \(base^{power}\)就可以这么写:
long long fastpower(long long base, long long power) {
long long ans = 1;
while (power > 0){
if ( power % 2 == 0 ){ //指数是偶数,直接除以二
power = power / 2; //指数缩小一半
base = base * base;//底数为原来的两次方
}
else{ //指数是奇数
power = power – 1; //先拆出一个base的一次方,拆完就变成偶数了
ans = ans * base ; //base的一次方乘进结果中
power = power / 2; //指数缩小一半
base = base * base;//底数为原来的两次方
}
}
return ans;
}
快速幂的优化
上述的算法在某些部分还可以进行优化
-
对于power = power – 1; 和power = power / 2; 这两句话,其实可以合并为一句话:power = power/2; 因为在这个分支中power一定是奇数,那么在C++中,奇数/2 也就是就是它-1再除以2 。
-
if 和 else 两个分支其实都要进行power = power / 2 和base = base * base 两个操作。
第二个power为奇数的分支其实就是多了个拆出一次幂的过程,那么我们可以在一开始就进行判断是否为奇数,如果是就拆出一次幂,如果不是就不做操作,然后进行两个分支都要进行的操作。
-
最后还有一个优化,在程序中需要判断的是是否为奇数和将这个数除以2的操作,这两个操作如果改用位运算的话,会显著提高计算效率。
对于任意一个数,如果它是奇数,那么它的二进制数最低位一定是1,反之一定是0;那么我们可以使用按位与操作,将这个数与1按位与,这样的话如果得数是1则说明数字是奇数,而得数是0的话说明该数是偶数,(1就是0000001,其高位全都是0,按位与操作得到的全都会是0) 然后对于a = a/2的操作,也可以用位运算 ,将一个数的二进制表示的每一位都向右移动一位,即为将其除以2。
快速幂的最简模板
为啥这里加了个mod呢?因为很多题的答案都是非常大的,可能long long都存不下,那这个时候题目就会要求你将所有的答案都以模p的形式给出来。p也就是下面代码中的mod。
typedef long long ll;
ll mod_pow(ll base ,ll pow , ll mod){
ll res = 1;
while(pow > 0){
if( pow & 1 ) res = res * base % mod; //pow是奇数
base = base * base % mod; //pow是偶数
pow >>= 1;
}
return res;
}
另外,印象中有一个题中,pow非常大,这个时候需要用到欧拉定理来进行降幂操作,直接用 pow % φ(n)。
具体细节想不起来了,想起来了再更新。