倍增关注的是指数级跨越(跳过冗余,化线为点)。
倍增依赖的是二进制拆分(任意数字都能唯一分解为
倍增的核心思想是预处理与动态组合(空间换时间,将线性复杂度降为对数级)。
一、倍增 (Binary Lifting) 核心定义
倍增字面意思是“成倍增长”。假设要走
通俗理解: “走台阶与传送门”。假设要去距离自己

二、核心应用模型:序列 步跳转 (K-step Jump)
剥离树形结构和图论,倍增最纯粹的形态就是在序列上的快速跳跃。
给定一个大小为
核心需求: 面对海量查询,每次询问“从位置
1. 标准求解算法与模板
下面约定 a[i] 都在 [1,n] 内,查询步数
f[j][i] 的含义和转移见下一节,这里先看一遍预处理与查询的整体轮廓。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100005;
int n,m,a[N],f[60][N];
void build(){
for(int i=1;i<=n;++i)f[0][i]=a[i];
for(int j=1;j<=59;++j)
for(int i=1;i<=n;++i)
f[j][i]=f[j-1][f[j-1][i]];
}
int query(int x,int k){
for(int j=0;j<=59;++j){
if((k>>j)&1)
x=f[j][x];
}
return x;
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i)cin>>a[i];
build();
while(m--){
int x,k;
cin>>x>>k;
cout<<query(x,k)<<'\n';
}
return 0;
}
三、核心原理推导 (State Transition & Query)
1. 状态定义
设 f[j][i] 表示从起点
边界条件 f[0][i] 就是从起点 a[i]。
2. 状态转移方程
跳跃
代数关系极其严密:
预处理的状态转移方程为:
外层循环枚举指数
3. 查询的二进制拆分
当面临任意步数
例如 1101,即
遍历 1,就让当前位置 f[j][x] 传送阵直接跨越
四、选学:视角的升维——函数复合与排列的 K 次方
先修要求:理解基础的映射与函数概念。
场景:很多题目并没有明显的“走路”背景,而是给你一个映射规则
物理意义:这在数学上叫函数的
降维打击:完全不需要提前学复杂的群论!只要把映射规则 a[i],然后直接套用倍增模板:f[j][i] = f[j-1][f[j-1][i]]。预处理完后,f[j][i] 就是对
五、进阶模型一:倍增维护路径信息(权值与最值)
问题场景:我们不仅要查询
状态含义:既然位置可以用倍增拆分,路上捡到的金币(权值)同样可以拆分!
新增灵魂数组 w[j][i]:表示从起点
关键推导:
跳跃依然是对半拆开,路径收益自然也就是前一半收益加上后一半收益。
状态转移方程非常严谨:
max 即可。)
手算小例子:
假设单步得分为权值。我们在一条直线上:1 -> 2 -> 3。
第一步,从 1 走到 2,得分 5,即 w[0][1] = 5,且 f[0][1] = 2。
第二步,从 2 走到 3,得分 8,即 w[0][2] = 8。
我们要算从 1 跳 2 步的得分 w[1][1]。根据推导公式:
w[1][1] = w[0][1] + w[0][f[0][1]] = w[0][1] + w[0][2] = 5 + 8 = 13。极其顺畅!
实现要点:
下面按 long long。不要无脑开到过高的层数,有环时高层的路径和可能先溢出。
在查询 x,一边累加权值 ans。注意铁律:必须先吃掉当前跳跃的权值,再真正移动 x 指针!
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100005;
int n, f[30][N], w[30][N];
void build() {
// 假设 f[0][i] (下一步位置) 和 w[0][i] (走这1步的收益) 已经在读入时初始化
// 0 号点作为越界“黑洞”,到达 0 之后再怎么跳都是 0,收益也是 0
for(int j=1; j<=29; ++j) {
for(int i=1; i<=n; ++i) {
f[j][i] = f[j-1][f[j-1][i]];
// 路径收益 = 前一半收益 + 后一半收益
w[j][i] = w[j-1][i] + w[j-1][f[j-1][i]];
}
}
}
int query_sum(int x, int k) {
int ans = 0;
for(int j=0; j<=29; ++j) {
if((k >> j) & 1) {
ans += w[j][x]; // 第一步:先把当前这段跳跃的收益吃掉
x = f[j][x]; // 第二步:再把位置指针真正挪过去
}
}
return ans;
}
signed main() {
ios::sync_with_stdio(0),cin.tie(0);
// 测试例子:3个点成环 1->2->3->1,单步收益分别为 10, 20, 30
n = 3;
f[0][1] = 2; w[0][1] = 10;
f[0][2] = 3; w[0][2] = 20;
f[0][3] = 1; w[0][3] = 30;
build();
// 从 1 出发走 4 步:轨迹是 1->2->3->1->2,收益是 10+20+30+10 = 70
cout << query_sum(1, 4) << '\n';
return 0;
}
六、进阶模型二:倍增找最远合法位置
问题场景:这是倍增最迷人的一种用法。此时步数
关键推导:
既然不知道该跳几步,我们就用二进制进行“贪心试探”。
我们从最大的跨度(例如
- 如果往前跳
步,发现没有越界且预算还没超,我们就果断吃掉这 步!扣除对应预算,起点向前挪。 - 如果往前跳
步发现超预算了,说明这一大步迈得太狠了,我们按兵不动,假装无事发生。 接着继续试探下一个稍小的跨度 。 这就好比用天平称重,先放最重的砝码,如果重了就拿下来,换轻一点的继续试。只要能放上去就一定放。
与答案二分的本质区别: 很多同学遇到“最大合法位置”会本能地想用二分。这两者有啥区别?
- 二分法:猜一个总步数
。为了验证这个 行不行,你每次都得从头开始走 步。如果在复杂结构上走 步很慢,二分就会直接超时。 - 倍增法:从大到小倒着试探。当试探
这段路时,我们直接调用预处理好的 w[j][x]瞬间得知花费,不需要从头走一遍!如果合法,起点就顺势推过去。这相当于边走边二分,全程只向一个方向推进,没有任何回溯重算的浪费。这也是倍增能把校验的时间复杂度也降下来的终极绝招。
实现要点:
这里约定每次有效跳转的花费是
与拼凑
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100005;
int n, f[30][N], w[30][N];
void build() {
for(int j=1; j<=29; ++j) {
for(int i=1; i<=n; ++i) {
f[j][i] = f[j-1][f[j-1][i]];
w[j][i] = w[j-1][i] + w[j-1][f[j-1][i]];
}
}
}
// 试探法:从 x 出发,在花费不超过 limit 的前提下,最多能走多少步?
int query_max_steps(int x, int limit) {
int steps = 0;
// 灵魂要点:必须倒序枚举!从大步往小步试
for(int j=29; j>=0; --j) {
// 如果往前跳 2^j 步没有越界(0表示越界点),且路径总花费没超标
if(f[j][x] != 0 && w[j][x] <= limit) {
limit -= w[j][x]; // 霸气扣除额度
steps += (1LL << j); // 步数直接累加上 2^j
x = f[j][x]; // 真正起跳,指针挪过去
}
}
return steps;
}
signed main() {
ios::sync_with_stdio(0),cin.tie(0);
n = 3;
// 测试例子:一条单向链 1 -> 2 -> 3 -> 0 (越界)
f[0][1] = 2; w[0][1] = 10;
f[0][2] = 3; w[0][2] = 20;
f[0][3] = 0; w[0][3] = 0;
build();
// 从 1 出发,兜里有 25 块钱预算。
// 第一步 1->2 花 10 块,剩下 15 块。
// 第二步 2->3 要 20 块,钱不够了!不跳。
// 所以最多只能走 1 步,剩余额度 15,停在点 2。
cout << query_max_steps(1, 25) << '\n';
return 0;
}
七、经典算法思想横向对比
| 比较维度 | 暴力枚举 (Brute Force) | 二分法 (Binary Search) | 倍增法 (Binary Lifting) |
|---|---|---|---|
| 推进方式 | 将整个区间折半缩小范围 | ||
| 前提要求 | 无特殊要求 | 必须具有单调性 | 状态必须具备可合并性/传递性 |
| 空间开销 | |||
| 核心特点 | 步步为营,不会遗漏 | 问答式探测,找临界点 | 拼凑法,二进制拆分组合 |
| 适用场景 | 小数据量走走模拟 | 有序空间中寻找最优解 | 大跨度状态转移、指数级跳跃 |