背包问题
背包问题是一类经典的动态规划问题,可以简单(数据量不大时)也可以很复杂(数据量很大时)。下面介绍几种典型背包问题,可以当做模板。
01背包问题
给定n个物品和一个容量为v的背包,求背包能装下的最大价值。特点是每个物品最多只能选择一次。
思考过程:
# include <bits/stdc++.h> using namespace std;
int main() { int N, V; cin >> N >> V; vector<int> volumes(N + 1), values(N + 1); for (int i = 1; i <= N; i++) { cin >> volumes[i] >> values[i]; } vector<int> dp(V + 1, 0); for (int i = 1; i <= N; i++) { for (int j = V; j >= 1; j--) { if (j >= volumes[i]) { dp[j] = max(dp[j], dp[j - volumes[i]] + values[i]); } } } cout << dp.back(); }
|
板子:
# include <bits/stdc++.h> using namespace std;
int main() { int N, V; cin >> N >> V; vector<int> dp(V + 1, 0); for (int i = 1; i <= N; i++) { int volume, value; cin >> volume >> value; for (int j = V; j >= volume; j--) { dp[j] = max(dp[j], dp[j - volume] + value); } } cout << dp.back() << endl; return 0; }
|
完全背包问题
-
完全背包问题的定义:给定一个背包容量和一组物品,每个物品可以选择多次,求最大价值。
-
完全背包问题的状态转移方程:dp[i][j]=max(dp[i−1][j],dp[i][j−w[i]]+v[i]),其中w[i]和v[i]分别是第i个物品的重量和价值。
当然这个二维也是可以优化到一维的,见代码:
# include <bits/stdc++.h> using namespace std;
int main() { int N, V; cin >> N >> V; vector<int> dp(V + 1, 0); for (int i = 0; i < N; i++) { int value, volume; cin >> volume >> value; for (int j = volume; j <= V; j++) { dp[j] = max(dp[j], dp[j - volume] + value); } } cout << dp.back() << endl; return 0; }
|
多重背包问题
这里是背包问题的重灾区之一,是对上面两个元问题的综合运用及运用各种优化手段。
I阶
问题主体和01背包问题一致,区别在于每种物品的数量不止1件,其他条件保持一致。数据量较小 N,V≤100, 即使O(n3)也不会超时
# include <bits/stdc++.h> using namespace std;
int main() { int N, V; cin >> N >> V; vector<int> volumes(N), values(N), count(N); for (int i = 0; i < N; i++) { cin >> volumes[i] >> values[i] >> count[i]; } vector<int> new_values = values, new_volumes = volumes; for (int i = 0; i < N; i++) { while (count[i] > 1) { new_values.push_back(values[i]); new_volumes.push_back(volumes[i]); count[i]--; } } values = new_values; volumes = new_volumes; vector<int> dp(V + 1, 0); for (int i = 0; i < values.size(); i++) { for (int j = V; j >= volumes[i]; j--) { dp[j] = max(dp[j], dp[j - volumes[i]] + values[i]); } } cout << dp.back(); return 0; }
|
II阶
问题与多重背包问题 I 完全一致,但数据范围变大了 N,V≤1000,不能简单地暴力拆分循环了
# include <bits/stdc++.h> using namespace std;
int main() { int N, V; cin >> N >> V; vector<int> volumes, values, count; for (int i = 0; i < N; i++) { int vo, va, co; cin >> vo >> va >> co; bool found = false; for (int j = 0; j < values.size(); j++) { if (volumes[j] == vo && values[j] == va) { count[j] += co; found = true; break; } } if (!found) { values.push_back(va); volumes.push_back(vo); count.push_back(co); } } N = values.size(); vector<int> new_volumes, new_values; for (int i = 0; i < N; i++) { int base = 1; while (count[i] > 0) { if (count[i] < base) { base = 1; continue; } count[i] -= base; new_values.push_back(values[i] * base); new_volumes.push_back(volumes[i] * base); base *= 2; } } values = new_values, volumes = new_volumes; vector<int> dp(V + 1, 0); for (int i = 0; i < values.size(); i++) { for (int j = V; j >= volumes[i]; j--) { dp[j] = max(dp[j], dp[j - volumes[i]] + values[i]); } } cout << dp.back() << endl; return 0; }
|
III阶
III阶很难,有余力再来掌握吧,花了很久看题解也并没有完全理解代码部分
问题与多重背包问题 II 完全一致,但数据范围更大了, N≤1000, V≤20000
本题的优化思路疑似有点太复杂了,贴一下 ref
# include <bits/stdc++.h>
using namespace std;
int main() { int N, V; cin >> N >> V; vector<int> volumes(N), values(N), count(N); for (int i = 0; i < N; i++) { cin >> volumes[i] >> values[i] >> count[i]; } vector<int> dp1(V + 1, 0), dp2(V + 1, 0); deque<int> q; for (int i = 0; i < N; i++) { dp2 = dp1; for (int mod = 0; mod < volumes[i]; mod++) { q.clear(); for (int j = mod; j <= V; j += volumes[i]) { while (!q.empty() && j - q.front() > count[i] * volumes[i]) { q.pop_front(); } while (!q.empty() && dp2[q.back()] + (j - q.back()) / volumes[i] * values[i] <= dp2[j]) { q.pop_back(); } q.push_back(j); dp1[j] = dp2[q.front()] + (j - q.front()) / volumes[i] * values[i]; } } } cout << dp1.back() << endl; return 0; }
|
带依赖的背包问题
01背包的进阶版,物品之间存在依赖关系,比如如果选了a则一定要选b。问题描述如下:
有 N个物品和一个容量是 V的背包。 物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。 第一行有两个整数 N, V, 用空格隔开,分别表示物品个数和背包容量。 接下来有 N行数据,每行数据表示一个物品。 第i行有三个整数 vi,wi,pi 用空格隔开,分别表示物品的体积、价值和依赖的物品编号。 如果 pi=−1, 表示根节点。数据保证所有物品构成一棵树
|
模板代码如下:
# include <bits/stdc++.h> using namespace std;
vector<int> graph[110]; int N, V, root;
void dfs(vector<vector<int>>& dp, vector<int>& volumes, vector<int>& values, int root) { for (int i = volumes[i]; i <= V; i++) { dp[root][i] = values[root]; } for (int child : graph[root]) { dfs(dp, volumes, values, child); int v = volumes[root]; for (int i = V; i >= v; i--) { for (int j = volumes[child]; i - j >= v; j++) { dp[root][i] = max(dp[root][i], dp[root][i - j] + dp[child][j]); } } } }
int main() { cin >> N >> V; vector<int> volumes(N + 1), values(N + 1); vector<vector<int>> dp(N + 1, vector<int>(V + 1, 0)); for (int i = 1; i <= N; i++) { int dep; cin >> volumes[i] >> values[i] >> dep; if (dep == -1) { root = i; } else { graph[dep].push_back(i); } } dfs(dp, volumes, values, root); cout << dp[root].back() << endl; return 0; }
|
背包问题方案数
01背包改版,要求求出最高价值的组合方案数,问题描述如下:
有 N件物品和一个容量是V的背包。每件物品只能使用一次。 第i件物品的体积是 vi,价值是 wi。 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出最优选法的方案数。注意答案可能很大,请输出答案模 10e9+7的结果。
|
代码如下:
#include <bits/stdc++.h> using namespace std; #define mod 1000000007
int main() { int N, V; cin >> N >> V; vector<int> dp(V + 1, 0); vector<int> count(V + 1, 1); for (int i = 0; i < N; i++) { int va, vo; cin >> vo >> va; for (int j = V; j >= vo; j--) { if (dp[j] < dp[j - vo] + va) { count[j] = count[j - vo]; dp[j] = dp[j - vo] + va; } else if (dp[j] == dp[j - vo] + va) { count[j] += count[j - vo]; } count[j] %= mod; } } cout << count.back() << endl; return 0; }
|
背包问题具体方案
01背包的改版,要求求出价值最高方案的具体组合方式,相同价值时要求方案的字典序最小,问题描述如下:
有N件物品和一个容量是V的背包。每件物品只能使用一次。 第i件物品的体积是 vi,价值是 wi。 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是1~N。
|
代码如下:
# include<bits/stdc++.h> using namespace std;
int compare(vector<int>& a, vector<int>& b) { for (int i = 0; i < min(a.size(), b.size()); i++) { if (a[i] < b[i]) { return -1; } if (a[i] > b[i]) { return 1; } } return (int)a.size() - (int)b.size(); }
int main() { int N, V; cin >> N >> V; vector<int> dp(V + 1, 0); vector<vector<int>> plans(V + 1); for (int i = 1; i <= N; i++) { int vo, va; cin >> vo >> va; for (int j = V; j >= vo; j--) { if (dp[j] < dp[j - vo] + va) { dp[j] = dp[j - vo] + va; plans[j] = plans[j - vo]; plans[j].push_back(i); } else if (dp[j] == dp[j - vo] + va) { vector<int> tmp = plans[j - vo]; tmp.push_back(i); if (compare(tmp, plans[j]) < 0) { plans[j] = move(tmp); } } } } for (int i : plans.back()) { cout << i << " "; } return 0; }
|
(•‿•)