快速乘法/幂 算法详解

快速幂运算

在计算 x时,我们会怎么算呢?如果只是x * x * x * ... * x 这样每个数乘起来计算 n 次的的话,虽然算法简单,但是复杂度为 O(n) ,往往不能满足要求。让我们来考虑加速幂运算的方法。

思路一:

如果 n = 2k ,可以将其表示为  xn = ((x2)2)... ,只要做 k 次平方运算就可以轻松求得。由此我们想到,先将 n 表示为2的幂次的和 n = 2k1 + 2k2 + 2k3 + ... ,就有 xn = x2^k1 x2^k2 x2^k3 ... ,只要在依次求 x2^i 的同时计算就好了,最终得到了 O(logn) 计算幂运算的算法。比如计算x22,可以把 x22 表示为 x* x4 * x16 (22转成二进制是10110)。在进行幂运算时,往往因为结果数字过大,而让我们输出取余后的结果。下面给出代码:

typedef long long ll;

ll mod_pow(ll x,ll n,ll mod){
    ll res = 1;
    while(n>0){
        if(n&1)  res = res * x % mod;  
      //如果二进制最低位为1,则乘上x^(2^i)
     x = x * x % mod;  //将x平方
     n >>= 1;
    }
    return res;
}

思路二:

当n为偶数时有 xn = ((x2)(n/2)) ,递归转为n/2的情况;n为奇数时有 xn = ((x2)(n/2)) * x ,同样也递归转为 n/2 的情况。这样不断递归下去,每次n都减半,于是可以在O(logn)时间内完成幂运算。这个比第一种似乎更容易想到也更易理解,下面给出代码:

typedef long long ll;

ll mod_pow(ll x,ll n,ll mod){
    if(n == 0) return 1;
    if(n == 1) return  x % mod;
    ll res = mod_pow(x * x % mod, n / 2, mod);
    if(n & 1)
        res = res * x % mod;
    return res;
}

思路三:  

还有一种非常类似的,f(x,n) = xn,x为奇数那么f(x,n) = f(x,n/2) * f(x,n/2) *x,x为偶数那么f(x,n) = f(x,n/2) * f(x,n/2)。

ll f(int x,int n){
    if(n==0) return 1;
    if(n==1) return x;
    if(n&1) return f(x,n>>1)*f(x,n>>1)*x;  //如果n是奇数
    else return f(x,n>>1)*f(x,n>>1);   //如果n是偶数
}

  

快速乘法

适用范围:快速计算a*b % mod的结果(主要目的是换乘法为加法,防止爆数据),或者快速计算a^b % mod 的结果,时间复杂度大大降低。

算法描述:首先你可能会问a*b不是直接乘就出来了么,为什么需要快速算法?但是乘法在计算机中处理的时间并不是这么快的,也要拆分为加法来做的。所以快速乘法会更快的计算a*b的结果,而且a*b%mod可能还没取模就已经爆long long,但快速乘法却不会。快速幂也是同样的道理。

实现的原理都是基于按照二进制位一步一步乘来避免重复的操作,利用前面的中间结果,从而实现快速的目的。

对于乘数b来说,势必可以拆成2进制,比如110101。有一些位为0,有一些位为1。根据乘法分配律:a*b=a*(b1+b2+b3+……)
那么对于a*53 = a*110101(二进制)= a*(100000+10000+100+1)=a*(100000*1+10000*1+1000*0+100*1+10*0+1*1)。
那么设立一个ans=0用于保存答案,每一位让a*=2,在根据b的对应为1看是不是加上此时的a,即可完成快速运算。比如刚才的例子让a=5,运转流程如下。

即可计算出5*53=265。

不知道看到这里你发现了没有,其实对于快速幂其实是一样的道理,只不过每一位a更新的时候不是*2,而是a=a*a,ans+变成ans*。
例如3^9的流程如下:3^5=3^(1001) (二进制)= 3^(1000*1+100*0+10*0+1*1)

最后说一些细节吧,如果要取模在ans+、ans*、和a更新的时候都%mod即可。然后b一位一位的读取每次b/=2或者b>>=1,表示b向右移动一位,再与1做&操作即可。(见代码)

#include <iostream> 
#include <cstdio> 
#include <algorithm>  
#include <cmath>  
#include <cstring>  
#include <map>  
using namespace std;

long long q_mul(long long a,long long b,long long mod)
// 快速计算 (a*b) % mod
{
	long long ans=0;
	while(b)
	{
		if(b&1)	//如果当前位为1
		{
			b--;
			ans =(ans+a)%mod;   //ans+=a
		}
		b>>=1;
		a=(a+a)%mod;
	}
	return ans;
}

long long q_pow(long long a,long long b,long long mod)
//快速计算 (a^b) % mod
{
	long long ans=1;
	while(b)
	{
		if(b&1)
			ans=q_mul(ans,a,mod); //ans*=a
		b>>=1;
		a=q_mul(a,a,mod);
	}
	return ans;
}

int main( )
{
	long long a, b, n;
	while(cin >> a >> b >> n)
	{
		cout << "a*b%n = " << q_mul( a, b, n ) << endl;
		cout << "a^b%n = " << q_pow( a, b, n ) << endl;
	}
	return 0;
}

 

// 本文部分参考:https://blog.csdn.net/maxichu/article/details/45459715
posted @ 2020-04-13 17:29  ジャスミン  阅读(1259)  评论(0编辑  收藏  举报