把每种宝物的件数做二进制拆分,转成若干件 0/1 物品后,再做一维 0/1 背包。
OJ: luogu
题目 ID: P1776
难度:普及+/提高
标签:动态规划多重背包背包
日期: 2026-06-19 22:22
题意
给出 n 种宝物。第 i 种宝物有:
- 价值
v_i - 重量
w_i - 数量
m_i
要求在总重量不超过 W 的前提下,使总价值最大。
思路
一句话本质:二进制拆分将多重背包转成 01 背包——
先看最直接的做法:
import sys
data = list(map(int, sys.stdin.buffer.read().split()))
n, W = data[0], data[1]
items = []
idx = 2
for _ in range(n):
v, w, m = data[idx], data[idx + 1], data[idx + 2]
idx += 3
items.append((v, w, m))
best = 0
def dfs(i, weight, value):
global best
if i == n:
best = max(best, value)
return
v, w, m = items[i]
for k in range(m + 1):
nw = weight + k * w
if nw > W:
break
dfs(i + 1, nw, value + k * v)
dfs(0, 0, 0)
print(best)brute.py 对每种物品枚举取
暴力为什么不能直接用 01 背包做?
01 背包要求每件物品只能选/不选,但这里每种物品有
能不能把
这就是二进制拆分的核心:任何整数
为什么这样正确?
对任意
拆分后怎么做?
对每种物品
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXW = 40005;
int n, W;
// dp[j] 表示容量为 j 时的最大总价值。
int dp[MAXW];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> W;
for (int i = 1; i <= n; i++) {
int v, w, m;
cin >> v >> w >> m; // 价值 v,重量 w,数量 m
// 二进制分组优化多重背包:把 m 拆成 1,2,4,... 的包。
int k = 1;
while (m > 0) {
int use = min(k, m);
int wv = use * w; // 包的重量
int vv = use * v; // 包的价值
// 每包作为 0/1 背包物品,倒序枚举。
for (int j = W; j >= wv; j--) {
dp[j] = max(dp[j], dp[j - wv] + vv);
}
m -= use;
k <<= 1;
}
}
cout << dp[W] << '\n';
return 0;
}复杂度
设拆分后的新物品总数为 M,则总时间复杂度是
总结
这题的关键不是背包状态本身,而是把“每种物品最多选 m_i 件”的限制高效压缩掉。二进制拆分是多重背包里最常用、也最稳的做法。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
