89. a^b
求 a
的 b
次方对 p
取模的值。
输入格式
三个整数 a,b,p
,在同一行用空格隔开。
输出格式
输出一个整数,表示a^b mod p
的值。
数据范围
$0≤a,b≤10^9$
$1≤p≤10^9$
输入样例:
3 2 7
输出样例:
2
思路:
文中的公式和表格等在这里好像会显示错误。
那我就贴一个本人博客链接吧 day27 89 a^b (快速幂)
这道题目乍一看会觉得并不难啊,题目短短一行而已,而且思路也很容易,求幂这种算法一般在初学程序设计语言的时候应该都有练习过,只要写一个简单的循环就能够搞定。
long Pow(long base,long power,int p){
long result = 1;
for(int i = 1;i <= power;i++){
result=result*base;
}
return result % p;
}
这样写貌似没有任何问题,但是有没有注意到a
和b
的范围是$0≤a,b≤10^9$,即$a^b$可能是$(10^9)^{10^9}$,这个数值用long
都无法存下来,既然无法存,题目怎么还要我们去求出$a^b$的结果并对p
取模呢?这不是胡闹吗?
别急,我们首先来了解一下“取模”运算的运算法则:(具体的证明感兴趣的同学可以问度娘)
- (a + b) % p = (a % p + b % p) % p (1)
- (a - b) % p = (a % p - b % p ) % p (2)
(a * b) % p = (a % p * b % p) % p (3)
其中我们只要关注第“3”
条法则即可:(a * b) % p = (a % p * b % p) % p
,我们会发现多个因子连续的乘积取模的结果等于每个因子取模后的乘积再取模的结果。也就是说,我们有:
(a * b * c) % d = ((a * b) % d * c % d) % d = ((a % d * b % d) % d * c % d) % d;
因此,我们可以借助这个法则,只需要在循环乘积的每一步都提前进行“取模”运算,而不是等到最后直接对结果“取模”,也能达到同样的效果。
所以,我们的代码可以变成这个样子:
long Pow(long base,long power,int p){
long result = 1 % p;
for(int i = 1;i <= power;i++){
result = result * base;
result = result % p;
}
return result % p;
}
思考时间复杂度
虽然这个求幂的方法很有用,计算结果也确实是正确的了,但是我们来考虑一下这个算法的时间复杂度,假设我们求2
的100
次方,那么将会执行100
次循环。如果我们分析一下这个算法,就会发现这个算法的时间复杂度为$O(N)$,其中$N$为指数。求一下小的结果还好,那如果我们要求2
的1000000000
次方呢?这个程序就可能会运行很久很久了。
因此是否可以继续优化一下?
假设我们有二进制数$b = (1110101)_2$,
那么它的十进制数值我们都知道是:
$b =1*2^0+0*2^1 +1*2^2+0*2^3+1*2^4+1*2^5+1*2^6$
即$b=2^0 +2^2+2^4+2^5+2^6$
那么$a^b=a^{(1110101)_2}$,由乘法的结合律就可以写成下面的式子:
$a^{(1110101)_2} = a^{2^0}* a^{2^2}*a^{2^4}*a^{2^5}*a^{2^6}$
此外我们还有下表中的规律:
| a的指数刚好是二进制b展开的每一项 | 后一项都是前一项的平方 |
|–|–|
| $a^{2^0}$| $a^{2^0}$ |
| $a^{2^1}$ | $(a^{2^0})^2$ |
| $a^{2^2}$|$(a^{2^1})^2$ |
| $a^{2^3}$ |$(a^{2^2})^2$ |
| $a^{2^4}$ |$(a^{2^3})^2$ |
由此我们可以根据b
的二进制表示中当前位是1
还是0
决定要不要乘上这一项,但不管要不要乘上这一项,a
都会变为a
的平方,方便下一轮的b
中下一个二进制位的计算。
好的,现在我们结合
(a * b) % p = (a % p * b % p) % p
与
$a^{(1110101)_2} = a^{2^0}* a^{2^2}*a^{2^4}*a^{2^5}*a^{2^6}$
以及上面表格描述的规律,便可以写出如下代码。
Java代码
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
long a = scanner.nextInt();
long b = scanner.nextInt();
long p = scanner.nextInt();
long res = 1 % p;//如果p为1的话,那么无论什么数的结果都是0;
while(b != 0){
if((b & 1) != 0) {//如果二进制下的最后一位是1
//res = (res % p) *(a % p) % p
res = res * a % p;
}
//a = (a % p) * (a % p) % p
a = a * a % p ;
b = b >> 1;
}
System.out.println(res );
}
}
其中的res = res * a % p;
与 a = a * a % p ;
可能难以理解。
$a^{(1110101)_2} = a^{2^0}* a^{2^2}*a^{2^4}*a^{2^5}*a^{2^6}$
假如我们乘到了$a^{2^2}$这一项,我们发现它自身就是$a^2*a^2$,那么如果$a^{2^2}$要对p
取模,就应该是
($a^2$%$p$)$*$($a^2$%$p$)% $p$,
同理括号中的$a^2$%$p$项中的$a^2$又是$a*a$,
故$a^2$%$p$ = $(a$ % $p)*(a$ % p)%$p$,
这便是代码中 a = a * a % p ;
的来由,
而res = res * a % p
也是一样,假如有多个乘积项,我们就会有
(a * b * c) % d = ((a * b) % d * c % d) % d = ((a % d * b % d) % d * c % d) % d;
一层套一层的,层层包好。
这里确实有点绕,本人能力有限,也不知道说清楚没有,有点只可意会不可言传的感觉。需要大家好好思考一下。