倍增法

倍增法(Binary Lifting)概述
倍增法(Binary Lifting)是一种高效的算法思想,主要用于跳跃式查询和倍增查询等问题。在解决问题时,倍增法可以将时间复杂度从线性 O(n) 减少到对数 O(log n)。
倍增法的核心思想是:
- 通过预处理阶段,存储跳跃的“倍数”关系(比如:2倍、4倍、8倍等)。
- 在查询阶段,利用这些预处理数据快速定位答案。
主要应用场景
- 树上最近公共祖先(LCA)查询
- 跳跃查询问题
- 比如:在链表或数组中,从某个位置开始,经过 k 步后会到达哪个位置。
- 区间快速查询
基本原理:利用二进制表示加速跳跃
给定一个查询问题,比如 “从某个点 u,经过 k 步到达哪个点?”
我们可以利用二进制的特性,将 k 分解成若干个 2ⁱ 的和。
例如:
- k=13,二进制表示为 1101。
- k = 8 + 4 + 1 = 2³ + 2² + 2⁰
思想:
我们只需要预处理出 “从某个点出发,跳跃 2ⁱ 步后的点”,然后在查询时,利用 k 的二进制表示组合这些 2ⁱ 步即可。
应用一:跳跃查询问题
问题描述
在一个数组或链表中,每个点指向一个下一位置。给定一个起点 u,我们想知道从 u 出发,经过 k 步会到达哪个位置?
解题步骤
-
预处理阶段:倍增表的构建
- 设
f[i][j]
表示从节点 i 出发,经过 2ʲ 步后到达的节点。
- 状态转移方程: f[i][j] = f[f[i][j-1]][j-1] 即:从节点 i 出发,走 2ʲ 步,可以拆分为走两次 2^(j-1) 步。
-
查询阶段:分解 k
- 将 k 表示为二进制的若干个 2ⁱ 之和。
- 对于 k 的每一位 1,累加对应的 2ⁱ 步数。
代码实现
假设有 n 个点,每个点有一个 next
指向它的下一位置。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100010;
const int LOG = 20;
int f[MAXN][LOG];
void preprocess(int n, vector<int>& next) {
for (int i = 1; i <= n; i++) {
f[i][0] = next[i];
}
for (int j = 1; j < LOG; j++) {
for (int i = 1; i <= n; i++) {
f[i][j] = f[f[i][j-1]][j-1];
}
}
}
int query(int u, int k) {
for (int j = 0; k > 0; j++) {
if (k & 1) {
u = f[u][j];
}
k >>= 1;
}
return u;
}
int main() {
int n = 5;
vector<int> next = {0, 2, 3, 4, 5, 5};
preprocess(n, next);
int start = 1, steps = 7;
cout << "从节点 " << start << " 经过 " << steps << " 步到达节点:" << query(start, steps) << endl;
return 0;
}
代码解释
-
预处理倍增表:
f[i][0]
表示从 i 出发,走1步到达的位置。
f[i][j]
根据状态转移方程构建。
-
查询:
- 将 k 的二进制表示逐位检查。
- 如果 k 的某一位为 1,就执行对应的 2ⁱ 步跳跃。
-
复杂度分析:
- 预处理时间复杂度: O(n log n)
- 单次查询时间复杂度: O(log k)
应用二:树上 LCA 问题
倍增法也可以用来求解树上两个节点的最近公共祖先(LCA)。方法类似,预处理每个节点的 2ⁱ 级父节点,然后快速跳跃到目标层次找到公共祖先。
总结
-
倍增法的核心思想:
利用二进制分解,将查询问题转换为若干次 2ⁱ 跳跃的组合。
-
主要步骤:
- 预处理阶段:构建倍增表。
- 查询阶段:利用倍增表加速跳跃。
-
时间复杂度:
- 预处理: O(n log n)
- 单次查询: O(log n)
倍增法是一种非常高效的算法技巧,在树结构、跳跃查询等场景中有广泛应用。
小结