day27 89 a^b (快速幂)

本文探讨了如何解决大数次方模运算的问题,当指数和底数达到10^9的量级时,常规方法无法直接计算。通过介绍取模运算的性质和二进制分解技巧,提出了一种优化算法,将时间复杂度降低,并给出了Java代码实现。该算法利用每次迭代时底数平方取模,以及每次乘法操作后立即取模的策略,有效应对大规模数值计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

89. a^b

ab 次方对 p 取模的值。

输入格式
三个整数 a,b,p,在同一行用空格隔开。

输出格式
输出一个整数,表示a^b mod p的值。

数据范围
0≤a,b≤1090≤a,b≤10^90a,b109
1≤p≤1091≤p≤10^91p109
输入样例:

3 2 7

输出样例:

2

思路:

这道题目乍一看会觉得并不难啊,题目短短一行而已,而且思路也很容易,求幂这种算法一般在初学程序设计语言的时候应该都有练习过,只要写一个简单的循环就能够搞定。

long Pow(long base,long power,int p){
    long result = 1;
    for(int i = 1;i <= power;i++){
        result=result*base;
    }
    return result % p;
}

这样写貌似没有任何问题,但是有没有注意到ab的范围是0≤a,b≤1090≤a,b≤10^90a,b109,即aba^bab可能是(109)109(10^9)^{10^9}(109)109,这个数值用long都无法存下来,既然无法存,题目怎么还要我们去求出aba^bab的结果并对p取模呢?这不是胡闹吗?
别急,我们首先来了解一下“取模”运算的运算法则:(具体的证明感兴趣的同学可以问度娘)

  1. (a + b) % p = (a % p + b % p) % p (1)
  2. (a - b) % p = (a % p - b % p ) % p (2)
  3. (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;
}
思考时间复杂度

虽然这个求幂的方法很有用,计算结果也确实是正确的了,但是我们来考虑一下这个算法的时间复杂度,假设我们求2100次方,那么将会执行100次循环。如果我们分析一下这个算法,就会发现这个算法的时间复杂度为O(N)O(N)O(N),其中NNN为指数。求一下小的结果还好,那如果我们要求21000000000次方呢?这个程序就可能会运行很久很久了。

因此是否可以继续优化一下?

假设我们有二进制数b=(1110101)2b = (1110101)_2b=(1110101)2,
那么它的十进制数值我们都知道是:
b=1∗20+0∗21+1∗22+0∗23+1∗24+1∗25+1∗26b =1*2^0+0*2^1 +1*2^2+0*2^3+1*2^4+1*2^5+1*2^6b=120+021+122+023+124+125+126

b=20+22+24+25+26b=2^0 +2^2+2^4+2^5+2^6b=20+22+24+25+26

那么ab=a(1110101)2a^b=a^{(1110101)_2}ab=a(1110101)2,由乘法的结合律就可以写成下面的式子:
a(1110101)2=a20∗a22∗a24∗a25∗a26a^{(1110101)_2} = a^{2^0}* a^{2^2}*a^{2^4}*a^{2^5}*a^{2^6}a(1110101)2=a20a22a24a25a26

此外我们还有下表中的规律:

a的指数刚好是二进制b展开的每一项后一项都是前一项的平方
a20a^{2^0}a20a20a^{2^0}a20
a21a^{2^1}a21(a20)2(a^{2^0})^2(a20)2
a22a^{2^2}a22(a21)2(a^{2^1})^2(a21)2
a23a^{2^3}a23(a22)2(a^{2^2})^2(a22)2
a24a^{2^4}a24(a23)2(a^{2^3})^2(a23)2

由此我们可以根据b的二进制表示中当前位是1还是0决定要不要乘上这一项,但不管要不要乘上这一项,a都会变为a的平方,方便下一轮的b中下一个二进制位的计算。

好的,现在我们结合
(a * b) % p = (a % p * b % p) % p

a(1110101)2=a20∗a22∗a24∗a25∗a26a^{(1110101)_2} = a^{2^0}* a^{2^2}*a^{2^4}*a^{2^5}*a^{2^6}a(1110101)2=a20a22a24a25a26
以及上面表格描述的规律,便可以写出如下代码。

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=a20∗a22∗a24∗a25∗a26a^{(1110101)_2} = a^{2^0}* a^{2^2}*a^{2^4}*a^{2^5}*a^{2^6}a(1110101)2=a20a22a24a25a26

假如我们乘到了a22a^{2^2}a22这一项,我们发现它自身就是a2∗a2a^2*a^2a2a2,那么如果a22a^{2^2}a22要对p取模,就应该是
a2a^2a2%ppp∗*a2a^2a2%ppp)% ppp,

同理括号中的a2a^2a2%ppp项中的a2a^2a2又是a∗aa*aaa
a2a^2a2%ppp = (a(a(a % p)∗(ap)*(ap)(a % p)%ppp,
这便是代码中 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;
一层套一层的,层层包好。
这里确实有点绕,本人能力有限,也不知道说清楚没有,有点只可意会不可言传的感觉。需要大家好好思考一下。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值