我的算法不可能这么简单—快速幂|取模

快速幂概念

幂运算 an 即 n 个 a 相乘。快速幂就是高效地算出 an

题目

洛谷 : P1226 【模板】快速幂||取余运算
在这里插入图片描述

快速幂算法实现

当 n 很大时,如 n = 109 ,计算时间会很长,且结果会很大,常常超过变量类型的最大值。下面先考虑如何缩短计算时间

解决结果过大超过变量范围的问题需要用到高精度

暴力思想

遇见这个问题,我相信你们的第一意识便是直接循环n次,让它乘就完了。逐个做乘法计算 an ,即计算 a×a×a×……a。

#include <bits/stdc++.h>
using namespace std;
#define int long long

//计算 a^n mod m 的朴素算法
int Pow(int a,int n,int m){
    int res = 1;
    while(n--)
        res = (res*a)%m;//关于取模会在后面介绍,这里先做个引子
    return res%m;
}
//将long long 重命名为int ,这里便不能用int main,好在c++提供了signed代替原int功能
signed main(){
    int b,p,k;
    cin>>b>>p>>k;
    printf("%lld^%lld mod %lld=%lld",b,p,k,Pow(b,p,k));

    return 0;
}

这样的代码非常容易理解,也非常容易实现,但是,尽管这样可以计算a的n次幂,但是计算的时间太长,往往会超时,蛤,这都能超时 , 你看

在这里插入图片描述

暴力法时间复杂度为 O(n),所以几乎是必超时的。

分治思想

对于 an 我们可以先算 a2 , 然后再计算 (a2)2 ,依次类推。也就是

当n为偶数时 , 将 an 分解为 an/2 × an/2 , 再将 an/2 分解为 an/4 ……
当n为奇数时 , 将 an 分解为 a(n-1)/2+1 × a(n-1)/2 , 然后依次类推 ……
这就是分治思想,将大问题分为几个小问题,算出小问题的答案,然后得出最终答案

//计算a^n
int qpow(int a,int n){
    if(n == 0)  return 1;
    if(n == 1)  return a;
    int temp = qpow(a,n/2);
    if(n%2 == 1)   
        return temp * temp * a;
    else    
        return temp * temp;
}

时间复杂度为 O(log2n),分治的代码还是感觉有点长,而且判断有点多。有没有更加容易理解而且更短的实现呢!

位运算思想

以a11 为例来说明位运算,a11 = a8 ×a2 ×a1 , 不难看出2,8…都是2的倍数即a1 ×a1=a2
,a2 × a2 = a4 ,a4 × a4 = a8 ,产生的都是倍乘关系,逐级递推就可以。如何将11分解为8+2+1,这便利用了二进制,11的二进制为1011,即23+21+20, 也即8+2+1,这样就完成了分解。
下面的代码便是完成这样的操作:

int qpow(int a,int n){
    int res = 1;
    while(n){
        if(n&1) res = res*a;//n&1 表示检测当前n的二进制的最后一位是不是1,如果是1就该开始乘了
        a = a*a;//无论是不是1,a都要乘一次
        n>>=1;//等价于n/=2,不过这样更快。
    }
    return res;
}

时间复杂度为 O(log2n),ok,我们发现位运算的快速幂代码很短,而且很好 背出来 写出来。并且这玩意长得很像朴素算法。。仅仅比朴素算法多了两行,带来的却是质的提升!

快速幂取模

由于幂运算的结果非常大,常常会超过变量类型的最大值,甚至超过内存所能存放的最大数,所以题目往往会要求对结果进行取模操作,缩小结果。

模运算公式

加 (a+b) mod m = ( (a mod m) + (b mod m) ) mod m
减 (a-b) mod m = ( (a mod m) - (b mod m) ) mod m
乘 (a×b) mod m = ( (a mod m) × (b mod m) ) mod m
然而,对除法进行类似 (a/b) mod m = ( (a mod m) / (b mod m) ) mod m 的操作是错误的,如(100/50) mod 20 = 2 , 而( (100 mod 20) / (50 mod 20) ) mod 20 = 0,两者并不相等,除法的取模需要用到逆元

显然由乘法的模运算公式可以推出 an mod m = (a mod m)n mod m
下面加上取模的算法都是依靠这个公式进行的

分治思想算法取模

int qpow(int a,int n,int m){
    if(n == 0)  return 1%m;
    if(n == 1)  return a%m;
    int temp = qpow(a,n/2,m);
    if(n%2 == 1)
        return (temp * temp * a) %m;
    else
        return (temp * temp) %m;
}

位运算思想算法取模

int qpow(int a,int n,int m){
    int res = 1;
    while(n){
        if(n&1) res = (res*a)%m;
        a = (a*a)%m;
        n>>=1;
    }
    return res%m;
}

题目代码

有了取模的快速幂,我们就能很愉快的A了这道例题!

#include <bits/stdc++.h>
using namespace std;
#define int long long

int qpow(int a,int n,int m){
    int res = 1;
    while(n){
        if(n&1) res = (res*a)%m;
        a = (a*a)%m;
        n>>=1;
    }
    return res%m;
}

signed main(){
    int b,p,k;
    cin>>b>>p>>k;
    printf("%lld^%lld mod %lld=%lld",b,p,k,qpow(b,p,k));

    return 0;
}

在这里插入图片描述

  • 所以今后遇到要求 a的n次方 的问题,顺手写个快速幂总没错的,毕竟,它这么短。
  • 19
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 26
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值