Appearance
L8_04 倍增法
一、倍增法的概念
1.1 倍增法定义
倍增法是一种基于二进制思想的算法,通过预处理将问题的时间复杂度从 O(n) 优化到 O(log n)。
1.2 倍增法的核心思想
- 将问题分解为若干个 2 的幂次部分
- 通过预处理存储中间结果
- 查询时通过二进制组合快速得到答案
1.3 倍增法的应用场景
- 求 LCA(最近公共祖先)
- 区间 RMQ(范围最值查询)
- 快速幂运算
- 树上路径查询
二、快速幂
2.1 快速幂算法
cpp
// 计算 a^b mod modu
long long fastPow(long long a, long long b, long long modu) {
long long result = 1;
a %= modu;
while (b > 0) {
if (b & 1) {
result = (result * a) % modu;
}
a = (a * a) % modu;
b >>= 1;
}
return result;
}2.2 矩阵快速幂
cpp
typedef vector<vector<long long>> Matrix;
Matrix multiply(const Matrix& a, const Matrix& b, long long mod) {
int n = a.size();
Matrix result(n, vector<long long>(n, 0));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
result[i][j] = (result[i][j] + a[i][k] * b[k][j]) % mod;
}
}
}
return result;
}
Matrix matrixPow(Matrix a, long long power, long long mod) {
int n = a.size();
Matrix result(n, vector<long long>(n, 0));
// 初始化单位矩阵
for (int i = 0; i < n; i++) {
result[i][i] = 1;
}
while (power > 0) {
if (power & 1) {
result = multiply(result, a, mod);
}
a = multiply(a, a, mod);
power >>= 1;
}
return result;
}2.3 快速幂应用:斐波那契数列
cpp
// 使用矩阵快速幂计算斐波那契数列第n项
long long fibonacci(long long n, long long mod) {
if (n <= 1) return n;
Matrix base = {{1, 1}, {1, 0}};
Matrix result = matrixPow(base, n - 1, mod);
return result[0][0];
}三、区间 RMQ(倍增法)
3.1 RMQ 预处理
cpp
const int MAXN = 100000;
const int LOG = 20;
int st[LOG][MAXN];
int logTable[MAXN];
void buildRMQ(const vector<int>& arr) {
int n = arr.size();
// 预处理 log 表
logTable[1] = 0;
for (int i = 2; i <= n; i++) {
logTable[i] = logTable[i / 2] + 1;
}
// 初始化 ST 表
for (int i = 0; i < n; i++) {
st[0][i] = arr[i];
}
// 预处理倍增
for (int j = 1; j < LOG; j++) {
for (int i = 0; i + (1 << j) <= n; i++) {
st[j][i] = min(st[j - 1][i], st[j - 1][i + (1 << (j - 1))]);
}
}
}
int queryRMQ(int l, int r) {
int len = r - l + 1;
int k = logTable[len];
return min(st[k][l], st[k][r - (1 << k) + 1]);
}3.2 区间最大值查询
cpp
void buildMaxRMQ(const vector<int>& arr) {
int n = arr.size();
for (int i = 0; i < n; i++) {
st[0][i] = arr[i];
}
for (int j = 1; j < LOG; j++) {
for (int i = 0; i + (1 << j) <= n; i++) {
st[j][i] = max(st[j - 1][i], st[j - 1][i + (1 << (j - 1))]);
}
}
}
int queryMaxRMQ(int l, int r) {
int len = r - l + 1;
int k = logTable[len];
return max(st[k][l], st[k][r - (1 << k) + 1]);
}四、LCA(最近公共祖先)
4.1 LCA 预处理
cpp
const int MAXN = 100000;
const int LOG = 20;
vector<int> adj[MAXN];
int depth[MAXN];
int up[LOG][MAXN];
void dfs(int u, int parent) {
up[0][u] = parent;
for (int v : adj[u]) {
if (v != parent) {
depth[v] = depth[u] + 1;
dfs(v, u);
}
}
}
void buildLCA(int root, int n) {
depth[root] = 0;
dfs(root, -1);
for (int j = 1; j < LOG; j++) {
for (int i = 0; i < n; i++) {
if (up[j - 1][i] == -1) {
up[j][i] = -1;
} else {
up[j][i] = up[j - 1][up[j - 1][i]];
}
}
}
}
int lca(int u, int v) {
// 确保 u 深度大于等于 v
if (depth[u] < depth[v]) {
swap(u, v);
}
// 将 u 提升到与 v 相同深度
for (int j = LOG - 1; j >= 0; j--) {
if (depth[u] - (1 << j) >= depth[v]) {
u = up[j][u];
}
}
if (u == v) return u;
// 同时向上提升直到找到 LCA
for (int j = LOG - 1; j >= 0; j--) {
if (up[j][u] != -1 && up[j][u] != up[j][v]) {
u = up[j][u];
v = up[j][v];
}
}
return up[0][u];
}4.2 LCA 应用:树上距离
cpp
int distance(int u, int v) {
int ancestor = lca(u, v);
return depth[u] + depth[v] - 2 * depth[ancestor];
}五、倍增法优化 DP
5.1 快速矩阵幂优化线性递推
cpp
// 递推式:f(n) = a * f(n-1) + b * f(n-2)
long long linearRecurrence(long long n, long long a, long long b, long long mod) {
if (n == 0) return 0;
if (n == 1) return 1;
Matrix base = {{a, b}, {1, 0}};
Matrix result = matrixPow(base, n - 1, mod);
return result[0][0];
}5.2 倍增法优化区间 DP
cpp
// 区间 DP 的倍增优化
// dp[i][j] 表示区间 [i, j] 的最优解
void buildIntervalDP(vector<int>& arr) {
int n = arr.size();
vector<vector<int>> dp(n, vector<int>(n, 0));
// 初始化长度为1的区间
for (int i = 0; i < n; i++) {
dp[i][i] = arr[i];
}
// 倍增预处理
for (int len = 2; len <= n; len <<= 1) {
for (int i = 0; i + len <= n; i++) {
int mid = i + len / 2;
dp[i][i + len - 1] = dp[i][mid - 1] + dp[mid][i + len - 1];
}
}
}六、倍增法的应用
6.1 二进制优化背包
cpp
// 多重背包问题的二进制优化
int knapsackBinaryOptimization(vector<int>& weights, vector<int>& counts,
vector<int>& values, int capacity) {
vector<int> w, v;
// 二进制拆分
for (int i = 0; i < weights.size(); i++) {
int cnt = counts[i];
for (int j = 1; j <= cnt; j <<= 1) {
w.push_back(weights[i] * j);
v.push_back(values[i] * j);
cnt -= j;
}
if (cnt > 0) {
w.push_back(weights[i] * cnt);
v.push_back(values[i] * cnt);
}
}
// 0-1背包
vector<int> dp(capacity + 1, 0);
for (int i = 0; i < w.size(); i++) {
for (int j = capacity; j >= w[i]; j--) {
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
return dp[capacity];
}6.2 跳表
cpp
// 跳表的倍增思想
class SkiplistNode {
public:
int val;
vector<SkiplistNode*> forward;
SkiplistNode(int v, int level) : val(v), forward(level, nullptr) {}
};
class Skiplist {
private:
SkiplistNode* head;
int maxLevel;
double p;
int randomLevel() {
int level = 1;
while (rand() < RAND_MAX * p && level < maxLevel) {
level++;
}
return level;
}
public:
Skiplist() : maxLevel(16), p(0.5) {
head = new SkiplistNode(-1, maxLevel);
srand(time(nullptr));
}
bool search(int target) {
SkiplistNode* curr = head;
for (int i = maxLevel - 1; i >= 0; i--) {
while (curr->forward[i] && curr->forward[i]->val < target) {
curr = curr->forward[i];
}
}
curr = curr->forward[0];
return curr && curr->val == target;
}
void add(int num) {
SkiplistNode* curr = head;
vector<SkiplistNode*> update(maxLevel);
for (int i = maxLevel - 1; i >= 0; i--) {
while (curr->forward[i] && curr->forward[i]->val < num) {
curr = curr->forward[i];
}
update[i] = curr;
}
int level = randomLevel();
SkiplistNode* newNode = new SkiplistNode(num, level);
for (int i = 0; i < level; i++) {
newNode->forward[i] = update[i]->forward[i];
update[i]->forward[i] = newNode;
}
}
bool erase(int num) {
SkiplistNode* curr = head;
vector<SkiplistNode*> update(maxLevel);
for (int i = maxLevel - 1; i >= 0; i--) {
while (curr->forward[i] && curr->forward[i]->val < num) {
curr = curr->forward[i];
}
update[i] = curr;
}
curr = curr->forward[0];
if (!curr || curr->val != num) return false;
for (int i = 0; i < maxLevel; i++) {
if (update[i]->forward[i] != curr) break;
update[i]->forward[i] = curr->forward[i];
}
delete curr;
return true;
}
};七、倍增法复杂度分析
| 操作 | 预处理时间 | 查询时间 | 空间复杂度 |
|---|---|---|---|
| 快速幂 | O(1) | O(log n) | O(1) |
| RMQ | O(n log n) | O(1) | O(n log n) |
| LCA | O(n log n) | O(log n) | O(n log n) |
| 矩阵快速幂 | O(1) | O(k³ log n) | O(k²) |
其中 k 为矩阵大小。
