快速幂问题

经典的快速幂问题讲的是这样一件事情:给定整数a,b,p, 求 a^b%p 的值,其中 0 < a,p < 10^8, 0 < b < 10^{18}.

题干很简洁,可是题目的难度却让和我一样的初学者望而却步,这道题目难度不在于思路的繁琐,而在于时间复杂度的庞大和越界的问题。

面对这样一个问题,我们最直接的想法是先求出 a^b 的值,在对 p 做取模运算,值得一提的是,由于题目数据的庞大,题解中涉及到的所有变量的类型均为 long long 类型,代码如下:

#include<iostream>
#include<cstdlib>
using namespace std;

int main(){
    long long a,b,p;
    cin>>a>>b>>p;
    long long ans = 1;
    while(b)
    {
        ans*=a;
        b--;
    }
    cout<<ans%p<<endl;
    return 0;
}

如前所述,这个代码有两个主要的问题:一是时间复杂度达到了O(n),这是十分庞大的;二是数据的越界问题,例如 a 的取值为 10^9 时,循环迭代三次以后 ans 的值就超出了 long long 表示的上限。

下面引入快速幂的思想,考虑这样一个例子:求 3^{10} 的值。除了一步一步乘 3 之外,也可以将 3^{10} 转化为 9^5 ,进一步考虑时,却遇到了麻烦:5是个奇数,无法再做分解。此时我们的解决方案是将 9^5 写为 9*9^4 ,将作为系数的 9 存到 ans 中,后面的 9^4 进行后续的迭代计算,直到某个时候指数一定会变成变成 0,原理是对于任意的b,一定有b = \sum {c_{k}}^{2^{k}},0<=k<=n,其中c_{k}^{}=0,1,此时的 ans 即为所求的答案。其实这个方法只解决了时间复杂度的问题,没有解决数据溢出的问题,所以我们要在循环的过程中尽可能多地对 p 做取模运算,基于的原理是(a*b)%p = (a%p)*(b%p)%p ,这个定理的正确性是显然的。此时代码如下:

#include<iostream>
using namespace std;

int main(){
    long long a,b,p,ans;
    cin>>a>>b>>p;
    
    a%=p;
    while(b)
    {
        if(b%2)
        {
            ans=(ans*a)%p;
        }
        a = (a*a)%p;
        b/=2;
    }
    cout<<ans<<endl;
    return 0;
}

  此时问题大体已经解决了,后续可以做一些优化,例如判断奇偶性和除2运算都可以通过位运算来缩短运行时间:如果 a&1 结果不为0,则 a 为奇数;a<<1 和 a>>1 分别对应左移和右移运算,即十进制中的乘 2 和除 2 运算,改进后的代码如下:

#include<iostream>
using namespace std;

int main(){
    long long a,b,p,ans;
    cin>>a>>b>>p;
    
    a%=p;
    while(b)
    {
        if(b&1)
        {
            ans=(ans*a)%p;
        }
        a = (a*a)%p;
        b>>=1;
    }
    cout<<ans<<endl;
    return 0;
}

 以上即为本人对快速幂问题的全部看法,代码纯手写,未经调试,如有问题,欢迎指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值