zoukankan      html  css  js  c++  java
  • 快速幂算法(二分思想减少连乘次数)

    快速幂是什么

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

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

  • 相关阅读:
    莫队总结
    三、模型层(二)
    二、模型层(一)
    五、web杂项
    一. Django入门
    二、js
    一、html和css
    二十一、正则表达式
    END:小练习、涨知识
    二十、协程
  • 原文地址:https://www.cnblogs.com/kevin-matrix/p/15179193.html
Copyright © 2011-2022 走看看