背包DP是线性DP中一种特殊的DP。
01背包
最基础的背包,有 \(n\) 件物品,背包容量为 \(V\),每件物品只有一件。可以使用空间优化,一般是原地滚动,此时注意容量需要从后往前更新,否则会一个状态更新多次。时间复杂度为 \(O(nV)\)。
核心代码
void solve() {
int n, V;
cin >> n >> V;
for (int i = 1; i <= n; i++) {
cin >> v[i] >> w[i];
}
for (int i = 1; i <= n; i++) {
for (int j = V; j >= v[i]; j--) { // 注意倒序更新
dp[j] = max(dp[j], dp[j - v[i]] + w[i]); // 体积均为j,取价值最大的
}
}
cout << dp[V] << "\n";
}
完全背包
有 \(n\) 件物品,背包容量为 \(V\),每件物品有无穷件。可以使用空间优化,一般是原地滚动,与01背包恰好相反,此时容量从前往后更新即可。复杂度为 \(O(nV)\)。
核心代码
void solve() {
int n, V;
cin >> n >> V;
for (int i = 1; i <= n; i++) {
cin >> v[i] >> w[i];
}
for (int i = 1; i <= n; i++) {
for (int j = v[i]; j <= V; j++) {
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
}
cout << dp[V] << "\n";
}
例题
完全背包问题
分组背包
有 \(n\) 组物品,背包容量为 \(V\),每组物品只能选一个。需要注意循环顺序,先对容量循环,再对每一组中的物品循环,因为每组中的物品相当于一组的一个状态,这些状态之间是互斥的。时间复杂度为 O(\(\sum n \times V\))。
核心代码
void solve() {
int N, V, S;
cin >> N >> V;
vector<int> v[N + 1], w[N + 1], dp(V + 1);
for (int i = 1; i <= N; i++) {
cin >> S;
v[i].resize(S);
w[i].resize(S);
for (int j = 0; j < S; j++) {
cin >> v[i][j] >> w[i][j];
}
}
for (int i = 1; i <= N; i++) {
for (int j = V; j >= 0; j--) {
for (int k = 0; k < v[i].size(); k++) {
if (j >= v[i][k]) {
dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]);
}
}
}
}
cout << dp[V] << "\n";
}
多重背包
有 \(n\) 组物品,背包容量为 \(V\),每组物品有有限个,其重量和价值相同。循环顺序为先对每一组中的物品循环,再对容量循环,若将多重背包中每组物品单独拿出来,可以看作01背包,而将物品进行组合可看作分组背包。时间复杂度为 O(\(\sum n \times V\))。可对每组中物品的组合进行优化,降低时间复杂度。
核心代码
void solve() {
int N, V;
cin >> N >> V;
vector<int> v(N + 1), w(N + 1), s(N + 1), dp(V + 1);
for (int i = 1; i <= N; i++) {
cin >> v[i] >> w[i] >> s[i];
}
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= s[i]; j++) {
for (int k = V; k >= v[i]; k--) {
dp[k] = max(dp[k], dp[k - v[i]] + w[i]);
}
}
}
cout << dp[V] << "\n";
}
例题:
多重背包问题 I