1.一般写法
简单暴力:直接上手,复杂度适用于a<10^ 9, b<10^ 6,1<m<10 ^9 求解 a b a^b ab%m
typedef long long LL;
LL pow(LL a,LL b,LL m){
LL ans = 1;
for(int i = 0;i<b;i++){
ans = ans*a%m;//这里的证明非常容易,可以自己试试
}
return ans;
}
算法时间复杂度显而易见:O(b)
2.快速幂的递归写法
第一个问题解决了,来看下这种规模看行不行:a< 1 0 9 10^9 109,b< 1 0 1 8 10^18 1018,1<m< 1 0 9 10^9 109,用上面的方法时间复杂度最高为 1 0 18 10^{18} 1018,显然不满足要求了,对这种情况仔细想想,似乎也只有O(log n)才能解决了,OK,它来了。
//相信天才如你,对这个算法那肯定是有手就行,推论?推论就不推了,注释?你见过狼人写注释嘛
typedef long long LL;
LL binarypow(LL a,LL b,LL m)
{
if(b == 0) return 1;
else if(b%2==1) return a*binarypow(a,b-1,m)%m;
else{
LL mul = binarypow(a,b/2,m);
return mul*mul%m;
}
}
为啥每算一步就%m,相信你能够理解,这么大个数相乘,那不得防溢出嘛。这个算法中间的细节其实值得仔细品味。1.对奇偶的讨论 2.保存中间变量,防止binarypow()*binarypow()这样的神仙操作,导致最后时间复杂度变差,学过位运算符的还可以再细一点,把判奇偶变为b&1,偶数为0,奇数为1。
3.快速幂的迭代写法
当然如果你不喜欢优美的递归,这里也有一种关于迭代的骚操作(主要是这上面推导很有意思)
typedef long long LL;
LL binarypow(LL a,LL b,LL m)
{//这里位运算用的妙,学过位运算的可以瞅瞅,没学过的可以走了(-_-)
LL ans = 1;
while(b>0){//如果b的二进制末尾为1,也可以写成b%2
if(b&1) ans = ans*a%m;//联系二进制,推荐用a^13来试一下,你就懂了,要不了5分钟
a =a*a%m; //推的时候不看%m
b>>=1; //b的二进制向左一位,把尾巴上的一位给删了
}
//关键在于全称盯着b的二进制,剩下就靠天了
return ans;
}
4.快速幂中的注意事项
说下上面的局限性:上面的算法呢,只考虑了m>1的情况,m = 1没有讨论,如果你直接把上面算法复制使用呢,希望你能看到这,否则自求多福吧(-_-)话不多少,m=1,那啥数对m取余都得为1呀,第二个细的很哦,要是a初始就大于m,在使用函数之前就可以让a%m,自己想想溢出这方面就懂了。那为啥能这么做,就得看%的性质了