背包问题是DP里面变化比较多的问题,可以参考网上的《背包9讲》,另外还是阅读《算竞入门》和《算竞进阶》,讲的最全的肯定是背包9讲,基本上把所有变形都讲了一遍,但是把问题讲的最清楚应该还是算竞进阶,特别是本篇的0-1背包。
进阶里面比较清晰的讲解了如何从二维数组变成滚动一维数组,讲解了为什么一维数组是倒序,而二维数组是顺序。进而也能很清晰的讲解完全背包问题。
OK,还是回到DP的转移方程,0-1背包的方程:
dp[i][j]=max{dp[i-1][j],dp[i-1][j-C[i]]+V}
简单的说就是当前选择前i个物品,耗费j个容量所能够取得最大价值。
如果不选择第i个物品,那么dp[i][j] = dp[i-1][j],
如果选择第i个物品,那么dp[i][j] = dp[i-1][j-C[i]] + V,因为选择了i,剩余容量只剩下j-C[i],价值+V。
这个方程算是背包问题里面最容易理解的。
接着需要理解如果从二维数组变成一维数组。
因为在迭代过程中,把i的阶段分为奇数和偶数2部分,每一次交替过程中,奇数部分会用到偶数部分的值,偶数部分会用到奇数部分的值,不会冲突,也就是说只用i={0,1}来进行循环即可。这样就去掉了i的这一个维度。
具体可以参考进阶书籍~
然后为什么一维数组是逆序的呢?
一维数组逆序是为了让j在降序的过程中使用i-1的迭代值,如果是升序,则会造成dp[j]使用了i的迭代值。一维数组是在二维数组逻辑下的代码优化,本质还是二维的!要考虑隐藏的i。
来看题目:
Problem Description
Many years ago , in Teddy’s hometown there was a man who was called “Bone Collector”. This man like to collect varies of bones , such as dog’s , cow’s , also he went to the grave …
许多年前,有个骨头收集爱好者BC,他收集了各种骨头,比如狗骨头、牛骨头。。。(自己脑补)
The bone collector had a big bag with a volume of V ,and along his trip of collecting there are a lot of bones , obviously , different bone has different value and different volume, now given the each bone’s value along his trip , can you calculate out the maximum of the total value the bone collector can get ?
BC有一个容量为V的袋子,骨头都是放在里面。骨头有不同的价值和大小,现在告诉你每个骨头的价值,你能算出这个袋子能够装的最大价值?
Input
The first line contain a integer T , the number of cases.
Followed by T cases , each case three lines , the first line contain two integer N , V, (N <= 1000 , V <= 1000 )representing the number of bones and the volume of his bag. And the second line contain N integers representing the value of each bone. The third line contain N integers representing the volume of each bone.
第一行是测试用例数量。
每个测试用例有3行,第一行是骨头数量N、背包大小V。
第二行是N个骨头的价值。
第三行是N个骨头的大小。
Output
One integer per line representing the maximum of the total value (this number will be less than 231).
Sample Input
1
5 10
1 2 3 4 5
5 4 3 2 1
Sample Output
14
0-1背包入门基础题。
源代码:C++
#include <limits.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <algorithm>#include <bitset>#include <iostream>#include <set>#include <string>#include <vector>using namespace std;int dp[1000][1000];int solve(int N, int V, vector<int> &values, vector<int> &volumes) {for(int i=1; i<=N; ++i){for(int j=volumes[i]; j<=V; ++j){dp[i][j] = std::max(dp[i-1][j], dp[i-1][j-volumes[i]] + values[i]);}}return dp[N][V];}int main() {#ifdef __MSYS__freopen("test.txt", "r", stdin);#endif// dp[i][j] = max{dp[i-1][j], dp[i-1][j-C[i]] + V}int n;int N, V;while (cin >> n) {while (cin >> N >> V) {vector<int> values(N, 0);vector<int> volumes(V, 0);for (int i = 0; i < N; ++i) {cin >> values[i];}for (int i = 0; i < N; ++i) {cin >> volumes[i];}cout << solve(N, V, values, volumes) << endl;}}return 0;}
一维代码:C++
#include <limits.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <algorithm>#include <bitset>#include <iostream>#include <set>#include <string>#include <vector>using namespace std;int dp[1000];int solve(int N, int V, vector<int> &values, vector<int> &volumes) {for(int i=1; i<=N; ++i){for(int j=V; j>=volumes[i]; --j){dp[j] = std::max(dp[j], dp[j-volumes[i]] + values[i]);}}return dp[V];}int main() {#ifdef __MSYS__freopen("test.txt", "r", stdin);#endif// dp[i][j] = max{dp[i-1][j], dp[i-1][j-C[i]] + V}int n;int N, V;while (cin >> n) {while (cin >> N >> V) {vector<int> values(N, 0);vector<int> volumes(V, 0);for (int i = 0; i < N; ++i) {cin >> values[i];}for (int i = 0; i < N; ++i) {cin >> volumes[i];}cout << solve(N, V, values, volumes) << endl;}}return 0;}
个人公众号(acm-clan):ACM算法日常
专注于基础算法的研究工作,深入解析ACM算法题,五分钟阅读,轻松理解每一行源代码。内容涉及算法、C/C++、机器学习等。