点击查看代码
/*
【倍增思想】快速幂(模板)
解法一:暴力循环 会超时
解法二:利用倍增 + 二进制
a^1 = a ------1
a^2 = a^1 * a^1 ------2
a^4 = a^2 * a^2 ------4
a^8 = a^4 * a^4 ------8
a^16 = a^8 * a^8 ------16
a^32 = a^16 * a^16
倍增思想的本质:把指数拆成二进制
任何整数 b 都可以拆成2 的幂次之和(二进制分解),比如:b=10 → 二进制 1010 → 10 = 8 + 2 = 2³ + 2¹因此 a^10 = a^(8+2) = a^8 * a^2(同底数幂相乘,指数相加)。
结合你列的倍增公式:
a^2 = a^1 * a^1(算 1 次)
a^4 = a^2 * a^2(算 1 次)
a^8 = a^4 * a^4(算 1 次)
计算 a^10 只需算 a^2 * a^8,总共只需要 3 次倍增 + 1 次乘法,而暴力需要 10 次乘法。
3. 二进制分解的通用规则
对任意指数 b:
把 b 转成二进制,每一位对应「是否需要乘上当前的倍增项」;
遍历 b 的二进制位,从低位到高位:
如果当前位是 1,就把当前的倍增项乘到结果里;
不管当前位是不是 1,都让倍增项自己倍增(a = a*a),对应二进制位权升级(2⁰→2¹→2²…);
b 右移一位(b = b/2),处理下一位。
***核心逻辑:把指数拆成二进制,遍历二进制的每一位:如果是 1,就把当前的倍增项乘到结果里;
不管是不是 1,都让倍增项自己乘自己(翻倍),直到指数拆完。
取模运算的规则:
1.当计算过程中,只有“加法”和“乘法”时,取模可以放在任意的位置
(a * b * c * d) % p = (a % p * b % p * c % p * d % p) % p = ((a * b % p * c) % p * d) % p
2.当计算过程中,存在“减法”时,结果是可能出现负数的,此时如果需要补正,就需要“模加模”的技巧来补正
(a - b) % p == ((a - b) % p + p) % p
3.当计算过程中,存在“除法”的时候,取模是会造成结果错误的
解决:求逆元
*/
#include <iostream>
using namespace std;
typedef long long LL;
LL qpow(LL a, LL b, LL p){
LL ret = 1; // 初始化结果为1(任何数的0次方是1,乘法的起点)
while(b){ // 等价于 while(b > 0),指数b未拆完就继续循环
// 步骤1:判断b的二进制最后一位是否为1(b&1是位运算,效率极高)
if(b & 1) ret = ret * a % p;
// 步骤2:底数倍增(a = a^2),并取模避免溢出
a = a * a % p;
// 步骤3:b右移一位(等价于b = b / 2),去掉最后一位,处理下一位
b >>= 1;
}
return ret; // 返回最终结果 (a^b) % p
}
int main(){
LL a, b, p;
cin >> a >> b >> p;
printf("%lld^%lld mod %lld=%lld", a, b, p, qpow(a, b, p));
return 0;
}