快速幂算法(二分思想减少连乘次数)

快速幂是什么

如果要我们求某个数的幂 (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} imes 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;
}


快速幂的优化

上述的算法在某些部分还可以进行优化

  1. 对于power = power – 1; 和power = power / 2; 这两句话,其实可以合并为一句话:power = power/2; 因为在这个分支中power一定是奇数,那么在C++中,奇数/2 也就是就是它-1再除以2 。

  2. if 和 else 两个分支其实都要进行power = power / 2 和base = base * base 两个操作。

    第二个power为奇数的分支其实就是多了个拆出一次幂的过程,那么我们可以在一开始就进行判断是否为奇数,如果是就拆出一次幂,如果不是就不做操作,然后进行两个分支都要进行的操作。

  3. 最后还有一个优化,在程序中需要判断的是是否为奇数和将这个数除以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)。

具体细节想不起来了,想起来了再更新。

原文地址:https://www.cnblogs.com/kevin-matrix/p/15179193.html