前言
快速幂算法
求a^b%c的值
其中a,b,c是整数,且0<a,c<1e9,0<b<1e18
分析
我们首先能考虑到的就是用循环直接计算a^b的值,然后再对c取模。代码如下
long long ans=1;
for(long long i=1;i<=b;i++)
{
ans*=a;
}
ans%=c;
但是这个代码有很大的缺陷,比如复杂度是O(b)而b的值是1e18,并且ans很有可能超过long long 所以该代码需要优化。
我们根据取模运算符的性质:(ab)%p=[(a%p)(b%p)]%p
证明:
设a=k1p+q1 b=k2p+q2
(ab)=(…)p+q1+q2
(ab)%p=(q1*q2)%p
a%p=q1 b%p=q2
现在我们可以每乘一次对p取模,但是现在仍然需要循环b次,那还有没有优化的方法呢?
当我们考虑手算3^10时我们可以怎样方便的计算呢
我们发现每次指数b都除2那么此时时间复杂度就降为O(log2b)
代码如下:
int fast_power(int a,int b,int c)
{
int ans=1;
a%=c;
while(b)
{
if(b%2==1)
{
ans=(ans*a)%c;
}
a=(a*a)%c;
b/=2;
}
return ans;
}
代码优化
int fast_power(int a,int b,int c)
{
int ans=1;
a%=c;
while(b)
{
if(b&1)
{
ans=(ans*a)%c;
}
a=(a*a)%c;
b>>=1;
}
return ans;
}
我们使用位运算符可以更加简化这个代码
b&1:
将 b 转换为二进制表示: 你需要先把 b 转换为二进制数。例如,如果 b 是一个整数 5,它的二进制表示是 101。
将 1 转换为二进制表示: 1 的二进制表示是 1。如果 b 是一个多位数,那么 1 应该用适当数量的零填充在前面以匹配 b 的位数。例如,如果 b 是 8 位的 00000101,那么 1 应该是 00000001。
对齐这两个二进制数: 如果 b 是多位的,将 1 填充到与 b 相同的位数。
执行位与运算: 对应位执行与运算。如果两位都是 1,结果是 1;否则,结果是 0。
例如:
b = 00000101 (二进制)
1 = 00000001 (二进制)
b & 1 = 00000001 (二进制)
将结果转换回十进制: 结果 00000001 转换回十进制就是 1。
所以,b & 1 计算的是 b 的二进制表示的最低位(最右边的一位)的值。具体来说,b & 1 的结果是 b 是否是奇数的指示:如果 b 是奇数,结果是 1;如果 b 是偶数,结果是 0。
b>>=1:
将 b 转换为二进制表示: 例如,如果 b 是 13,它的二进制表示是 1101。
执行右移运算: 将二进制数向右移动一个位置。右移时,最右边的位将被丢弃,左边会根据符号位填充。
对于无符号整数,左边的位填充为 0。
对于有符号整数(例如,32 位带符号整数),右移时左边的位根据符号位(即最高位)来填充。对于正数,填充 0;对于负数,通常使用算术右移(填充符号位)。
例如,13 的二进制表示是 00001101。右移一位后变成 00000110。
计算结果: 移动后的结果就是 b 除以 2 的整数部分。对于 13,右移一位得到的结果是 6,因为 13 / 2 = 6。
具体例子:
b = 13 的二进制是 00001101。
b >>= 1 后变成 00000110,即十进制的 6。
对于负数,例如 b = -5(假设使用 8 位二进制表示,补码表示),其二进制表示是 11111111(因为 -5 的补码是 11111111)。右移一位后变成 11111111(因为是负数,使用算术右移,符号位填充 1)。
计算结果为 -3,因为 -5 除以 2 的结果是 -3。