(4) 倍增法
GESP C++ 八级考试大纲知识点梳理系列文章:
- 计数原理:加法与乘法
- 排列与组合
- 杨辉三角与组合数
- 倍增法
- 代数与平面几何 {: .prompt-tip}
继上一篇我们探讨了杨辉三角与组合数之后,我们继续深入 GESP C++ 八级大纲。今天的主角是算法竞赛中极其常用且高效的思想——倍增法。
(4)掌握倍增法概念。了解倍增法的时间复杂度。 {: .prompt-info}
倍增法(Doubling Method)不仅仅是一个特定的算法,更像是一种“思想”。它的核心在于“成倍增长”,利用二进制的性质,将线性级别的处理转化为对数级别的处理,极大地优化了时间复杂度。
本人也是边学、边实验、边总结,且对考纲深度和广度的把握属于个人理解。因此本文更多的不是一个教程,而是个人知识梳理,如有遗漏、疏忽,欢迎指正、交流。 {: .prompt-warning}
一、什么是倍增法?
顾名思义,倍增就是“成倍增加”。
想象一下,你要从起点 A 走到终点 B。
- 普通方法:一步一步走,每次走 1 米。如果距离是 米,需要走 步。时间复杂度 。
- 倍增方法:通过“预处理能力”,我们可以一次跳 米。
- 如果距离是 15 米,我们可以先跳 8 米,再跳 4 米,再跳 2 米,再跳 1 米()。
- 总共只需要跳 4 次,而不是 15 次。
核心原理:
任何一个正整数 都可以表示为若干个 的幂次之和(这就是二进制的本质)。 例如 。 因此,我们可以通过组合这些 的跳跃,快速到达任何目标位置。
1.1 时间复杂度
倍增法的核心优势在于速度。 对于范围 ,我们只需要考虑 (其中 ) 也就是 级别的数据。 因此,倍增法相关的查询操作通常具有 的时间复杂度。
二、倍增法的应用场景
快速幂 (Quick Pow)
这是倍增思想最基础、最典型的应用,用于快速计算 。
1. 核心思想:二进制拆分
普通做法:
写一个循环,执行 次乘法。时间复杂度 。当 很大(如 )时,绝对会因超时(TLE)而得分 0。
倍增做法:
利用 的二进制表示,我们将 拆解为若干个 的和。 例如计算 :
所以:
你会发现,每一项 都是前一项的平方。
我们只需要简单的 次乘法,就可以算出结果。
2. 代码示例 (C++)
/**
* 快速幂计算 a^b % p
* 时间复杂度:O(log b)
* 空间复杂度:O(1)
*/
long long quick_pow(long long a, long long b, long long p) {
long long ans = 1; // 记录最终结果
a = a % p; // 预先取模,防止 a 很大导致溢出
while (b > 0) {
// 核心逻辑:判断 b 的二进制最后一位是 0 还是 1
// 如果是 1 (即 b 为奇数),说明当前的权重 a 需要乘进结果里
if (b & 1) {
ans = (ans * a) % p;
}
// a 自身倍增:a^1 -> a^2 -> a^4 -> a^8 ...
// 无论当前位是否为 1,权值 a 都要翻倍等待下一位使用
a = (a * a) % p;
// b 右移一位,相当于除以 2,处理下一位二进制
b >>= 1;
}
return ans;
}为了让大家更清晰地理解代码与上述数学原理的对应关系,我们可以看下面这个演示图:
b & 1(取当前最低位):这是在检查 的二进制表示中,当前处理到的这一位是 0 还是 1。- 如果是 1:说明指数 包含当前的 (例如 中的 )。因此,我们需要把当前的底数
a(此时它的值是 ) 乘进结果ans里。 - 如果是 0:说明不需要这一项,直接跳过。
- 如果是 1:说明指数 包含当前的 (例如 中的 )。因此,我们需要把当前的底数
a = a * a(基数倍增):对应 的过程。无论b的当前位是不是 1,底数a都要不断翻倍,为下一位做准备。b >>= 1(右移):对应二进制位的遍历,从低位向高位移动。
演示:计算
| 循环轮次 | 当前 b (二进制) | 当前底数 a | 判断 b & 1 | 操作 | ans 变化 |
|---|---|---|---|---|---|
| 初始 | 1101 (13) | 是 | 乘入 ans | ||
| 第 1 轮 | 110 (6) | 否 | 不乘 | 不变 | |
| 第 2 轮 | 11 (3) | 是 | 乘入 ans | ||
| 第 3 轮 | 1 (1) | 是 | 乘入 ans | ||
| 结束 | 0 | - | - | - |
3. 其他应用拓展举例
快速幂不仅仅用于算“幂”,它的本质是“利用二进制加速递推”。常见的变形和应用包括:
(1) 模运算乘法逆元 (Modular Multiplicative Inverse) 在计算组合数 或有理数取模时,需要做除法 。 但是模运算没有除法()。 根据费马小定理,我们可以把除法转为乘法:
费马小定理:如果 是质数,且 不是 的倍数,则 。
符号解释:“” 是同余符号。这句话的通俗意思是: 除以 的余数是 1。
变形一下:。 这说明 就是 在模 意义下的倒数(即逆元)。 所以:。
即求 的 次方。这是快速幂最高频的使用场景。
(2) 矩阵快速幂 (Matrix Exponentiation) 如果把数字 换成一个矩阵 ,把乘法换成矩阵乘法,原理一模一样。 这用于解决 递推问题(如斐波那契数列、线性递推式)加速到 。
其中矩阵的 次幂就用快速幂求解。
(3) 快速乘 (Quick Mul)
当 中,和都高达 ,直接相乘会爆 long long 范围。
我们可以把“乘法”看作是“加法的幂”,用类似快速幂的逻辑(把 换成 ):
。
通过倍增 来实现大数相乘取模。
(4) 字符串 Hash / 滚动 Hash 在计算字符串 Hash 值时,经常需要计算 ,为了快速获取子串 Hash,需要快速幂预处理或实时计算 的幂。
三、总结
倍增法是解决很多“线性变对数”问题的金钥匙。
- 核心:利用二进制拆分,将 次操作压缩为 次。
掌握了倍增,你不仅能解决 8 级的考点,更是在向高级算法思维迈进了一大步。
本文由coderli.com原创,按照CC BY-NC-SA 4.0 进行授权
所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code
“luogu-”系列题目可在 洛谷题库 在线评测。
“bcqm-”系列题目可在 编程启蒙题库 在线评测。
GESP/CSP认证交流QQ群: 688906745
