经典的快速幂问题讲的是这样一件事情:给定整数a,b,p, 求 的值,其中 0 < a,p < , 0 < 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;
}
如前所述,这个代码有两个主要的问题:一是时间复杂度达到了,这是十分庞大的;二是数据的越界问题,例如 a 的取值为 时,循环迭代三次以后 ans 的值就超出了 long long 表示的上限。
下面引入快速幂的思想,考虑这样一个例子:求 的值。除了一步一步乘 3 之外,也可以将 转化为 ,进一步考虑时,却遇到了麻烦:5是个奇数,无法再做分解。此时我们的解决方案是将 写为 ,将作为系数的 9 存到 ans 中,后面的 进行后续的迭代计算,直到某个时候指数一定会变成变成 0,原理是对于任意的b,一定有,其中,此时的 ans 即为所求的答案。其实这个方法只解决了时间复杂度的问题,没有解决数据溢出的问题,所以我们要在循环的过程中尽可能多地对 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;
}
以上即为本人对快速幂问题的全部看法,代码纯手写,未经调试,如有问题,欢迎指正。