一、二进制优化的核心思想
1. 基本原理
把一个正整数s拆分成若干个2的幂次方(1, 2, 4, 8, ...)的和,再加上剩余的零头。
例如:s = 13
拆分成:1 + 2 + 4 + 6
这样就可以用4个组表示0-13的所有数字
2. 数学原理
任何一个正整数n都可以表示为:
n = a₀·2⁰ + a₁·2¹ + a₂·2² + ... + aₖ·2ᵏ + r
其中aᵢ是0或1,r是剩余部分(小于2ᵏ⁺¹)
二、二进制优化的方法
步骤1:物品拆分
对于每种物品(体积v,价值w,数量s):
vector<pair<int, int>> items; // 存储拆分后的物品(体积,价值) int k = 1; // 从1开始 while (s > 0) { int amount = min(k, s); // 这一组的数量 items.push_back({v * amount, w * amount}); // 打包成一组 s -= amount; k *= 2; // 幂次增加 }步骤2:转换为01背包
拆分后,对每组物品使用01背包算法:
for (auto &item : items) { int v_group = item.first; // 这一组的总体积 int w_group = item.second; // 这一组的总价值 for (int j = V; j >= v_group; j--) { dp[j] = max(dp[j], dp[j - v_group] + w_group); } }三、使用要点
1.什么时候使用二进制优化?
情况1:物品数量s很大(比如s ≥ 20)
情况2:背包容量V较大(V ≥ 1000)
情况3:物品种类n较多(n ≥ 50)
2.二进制优化的优点
| 原始方法 | 二进制优化后 |
|---|---|
| 每个物品拆成s个 | 每个物品拆成log₂(s)个 |
| 时间复杂度:O(n × s × V) | 时间复杂度:O(n × log(s) × V) |
| 物品总数 = ∑s | 物品总数 = ∑log₂(s) |
3.拆分的边界情况
// 正确写法 while (k <= s) { items.push_back({v * k, w * k}); s -= k; k *= 2; } if (s > 0) { items.push_back({v * s, w * s}); }四、适用场景分析
场景1:多重背包问题
// 问题描述:n种物品,每种物品有体积v[i],价值w[i],数量s[i] // 背包容量V,求最大价值
✅必须用二进制优化的情况:
n = 100, s[i] = 1000, V = 1000
计算量:100×1000×1000 = 1亿 → 100×10×1000 = 100万(优化100倍)
场景2:完全背包问题
// 完全背包:每种物品可以取无限次
❌不能用二进制优化,需要用完全背包的递推公式:
for (int j = v[i]; j <= V; j++) { dp[j] = max(dp[j], dp[j - v[i]] + w[i]); }场景3:混合背包问题
// 有些物品只能取1次(01背包) // 有些物品可以取无限次(完全背包) // 有些物品可以取有限次(多重背包)
✅部分用二进制优化:
if (s == -1) // 01背包 // 直接处理 else if (s == 0) // 完全背包 // 完全背包处理 else // 多重背包 // 二进制优化后处理
五、代码模板
#include <iostream> #include <vector> using namespace std; int main() { int n, V; cin >> n >> V; vector<int> dp(V + 1, 0); vector<pair<int, int>> items; // 存放(体积, 价值) // 读取并拆分物品 for (int i = 0; i < n; i++) { int v, w, s; cin >> v >> w >> s; // 二进制拆分 int k = 1; while (k <= s) { items.push_back({v * k, w * k}); s -= k; k *= 2; } if (s > 0) { items.push_back({v * s, w * s}); } } // 01背包 for (auto &item : items) { for (int j = V; j >= item.first; j--) { dp[j] = max(dp[j], dp[j - item.first] + item.second); } } cout << dp[V] << endl; return 0; }六、复杂度分析
优化前后对比
假设:n=100, 平均s=1000, V=1000
| 方法 | 物品总数 | 循环次数 | 时间估算 |
|---|---|---|---|
| 朴素拆分 | 100×1000=100,000 | 100,000×1000=1亿 | 约1秒 |
| 二进制优化 | 100×10=1,000 | 1,000×1000=100万 | 约0.01秒 |
七、注意事项
1.不要过度拆分
如果s很小(比如s ≤ 10),直接朴素拆分即可,二进制优化反而增加复杂度。
2.注意数据范围
// 错误:可能会溢出 items.push_back({v * k, w * k}); // 正确:确保在int范围内 int group_v = v * k; int group_w = w * k; if (group_v > V) break; // 如果单组体积超过背包容量,可以跳过 items.push_back({group_v, group_w});3.优化技巧
// 提前剪枝:如果物品总体积 > 背包容量,直接按完全背包处理 if (v * s >= V) { // 转换为完全背包 for (int j = v; j <= V; j++) { dp[j] = max(dp[j], dp[j - v] + w); } continue; }八、总结口诀
物品数量多,拆分要巧妙, 二进制优化,效率提高高。 二幂来打包,零头单独搞, 转成01包,问题解决了。
记住:当s > 20时,考虑二进制优化;当s很小或V很小时,直接朴素拆分即可。