目录
引入
给定三个正整数a, b, m (a < 10^9, b < 10^6, 1 < m < 10^9), 求 a^b % m。
注:^表示乘方运算符
你可能觉得,咱只要学过循环不就会写(有手就行)
没错,这里给一个时间复杂度为O(b)的解法
朴素解法
typedef long long LL;
LL fn(LL a, LL b, LL m){
LL ans = 1;
for(int i = 0; i < b; ++i){
ans = ans * a % m;
}
return ans;
}
接下来,问题更进一步:
给定三个正整数a, b, m (a < 10^9, b < 10^18, 1 < m < 10^9), 求 a^b % m。
对于O(b)的复杂度,支持b < 10^8都已经困难,罔论10^18
那怎么办呢?
快速幂:
由于它基于二分的思想,也被称为二分幂
快速幂基于以下事实:
1. 如果b是奇数,a^b = a * a^b-1;
2. 如果b是偶数, a^b = a^(b/2) * a^(b/2);
因此,对于一个b,我们基于上述事实可以把问题的范围从a^b一直缩小到a^0。
没错,就是递归
快速幂的递归写法:
typedef long long LL;
LL binaryPow(LL a, LL b, LL m){
if(b == 0) return 1;
if(b % 2 == 1) return a * binaryPow(a, b-1, m) % m;
else{
LL tmp = binaryPow(a, b/2, m);
return tmp * tmp % m;
}
}
请您注意两点:
1. 初始的时候,a可能大于m,那需要在进入函数前先对a取模;
2.如果m = 1,可以直接结果特判为0,因为任何正整数 % 1均为0;
快速幂的迭代写法
对a^b来说,b可以写成若干二进制之和,如13 = 2^3 + 2^2 +2^0 = 8 + 4 + 1,所以2^13 = 2^8 + 2^4 + 2^1。
很容易得知,我们其实可以把任意a^b表示成a^2k, ..., a^1的乘积
如果b的二进制的i号位为1,那么a^2^i这项就会被选中。
因此,我们得到了计算a^b的大致思路:令i从0到k枚举b的二进制的每一位,如果当前位为1,那么我们就把a^2^i这项捡起来。
不难注意到,a^2k, ..., a^4, a^2, a^1的前一项总是等于后一项的平方,
所以可以用如下策略来具体实现:
1. 初始的时候令ans = 1, 用来存放累积的结果;
2. 判断b的二进制末尾是否为1(即判断b & 1是否为1, 也可以理解为判断是否为奇数),如果是的话,令ans乘上a的值;
3. 令a平方,并将b右移一位(也可以理解为b/2)
4. 只要b大于0,就返回第2步。
代码如下:
typedef long long LL;
LL binaryPow(LL a, LL b, LL m){
LL ans = 1;
while(b > 0){
if(b & 1){
ans = ans * a % m;
}
a = a * a % m;
b >>= 1;
}
return ans;
}