背包一讲
视频链接:背包九讲专题_哔哩哔哩_bilibili
一,01 背包问题
1,二维代码
f [ i ][ j ] 表示只考虑到前 i 个物品,且总体积恰好等于 j 的情况下,背包的最大价值。
递推式:
如果不选第 i 个物品,则 ①:f [ i ][ j ] = f [ i - 1 ][ j ]
如果选择第 i 个物品,则 ②:f [ i ][ j ] = f [ i - 1 ][ j - v[i] ] + w[ i ]
f [ i ][ j ] = max(①, ②)
在写代码时,可以写成下面这种比较简洁。
1 f[i][j] = f[i - 1][j]; // 默认不选 2 if (v[i] <= j) // 如果可以选,即物品体积 <= 背包体积 3 f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
2,一维代码:01 背包的空间优化
因为在二维的 f 中,f[ i ][ ] 只与 f[ i-1 ][ ] 有关,所以可以将 f 数组压缩成一维。
设置滚动数组 f [ j ] 表示:
如果当前考虑的是第 i 个物品,那么在总体积小于等于 j 的情况下,只考虑到前 i 个物品的背包的最大价值是 f[ j ]。
注意点:为什么内层循环要从后往前递推呢?
因为 f[ j ] 是滚动数组,在实际代码运行的时候,针对每个 i,f[ ] 的值都会被刷新。
而需要注意的是,当 i++ 后,进入一轮新的循环的时候,此时 f[ j ] 针对的是前 i-1 个物品。因为此时的 f[ j ] 还没有刷新,变成针对前 i 个物品。
所以有:
Ⅰ如果是从前往后遍历的话,那么 f[ j ] 肯定没经过刷新,代表的是针对前 i-1 个物品,而 ① 要求的是针对前 i 个物品,所以不行。
且有 f[ j - v[ i ] ] 肯定经过刷新,代表的是针对前 i 个物品,而 ② 要求的是针对前 i-1 个物品,所以不行。
Ⅱ 如果是从后往前遍历的话,那么 f[ j ] 肯定经过刷新,代表的是针对前 i 个物品,与 ① 的要求是相同的,所以可以。
且有 f[ j - v[ i ] ] 肯定没经过刷新,代表的是针对前 i-1 个物品,与 ② 的要求是相同的,所以可以。
递推式:
从针对 i-1 个物品的状态,推导针对前 i 个物品的状态。
原先,如果不选第 i 个物品,则 f [ i ][ j ] = f [ i - 1 ][ j ] ,
此时,压缩成一维后,未刷新的 f[ j ] 等同于 f [ i - 1 ][ j ],
所以有,
①:f [ j ] = f [ j ]
原先,如果选择第 i 个物品,则 f [ i ][ j ] = f [ i - 1 ][ j - v[i] ] + w[ i ]
此时,压缩成一维后,未刷新的 f[ j - v[i] ] 等同于 f[ i - 1 ][ j - v[i] ],
所以有,
②:f [ j ] = f[ j - v[i] ] + w[ i ]
最后,f [ i ][ j ] = max(①, ②)
在写代码时,可以写成下面这种比较简洁。
1 for (int i = 1; i <= n; i++) 2 for (int j = m; j >= v[i]; j--) 3 f[j] = max(f[j], f[j - v[i]] + w[i]);
3,f[] 初始化不同引起的含义不同:
对于一维的代码:
① 初始化为:f[ j ] == 0;
此时 f[ j ] 代表如果当前考虑的是第 i 个物品,那么在总体积小于等于 j 的情况下,只考虑到前 i 个物品的背包的最大价值是 f[ j ]。
② 初始化为:f[ 0 ] == 0;f[ j ] == -inf;
此时 f[ j ] 代表如果当前考虑的是第 i 个物品,那么在总体积恰好等于 j 的情况下,只考虑到前 i 个物品的背包的最大价值是 f[ j ]。
证 :
如果是 ①
Ⅰ 我们可以从 f[ 0 ] == 0,根据 f[ v[i] ] = max(f[ 0 ], w[i]) ,推导出所有 f[ j ]。
Ⅱ 设有 k <= j <= m,因为初始时 f[ k ] == 0,
我们也可以从 f[ k ] == 0,根据 f[ k + v[i] ] = max(f[ k ], w[i]) ,推导出所有 f[ j ]。
即,f[ j ] 可能是由 f[ 0 ] 推导出来的,则此时 f[ j ] 所需的体积必然等于 j。
但,f[ j ] 也可能是由 f[ k ] 推导出来的,即此时 f[ j ] 所需的体积为 j - k。
综上,f[ j ] 所需的体积小于等于 j。所以 f[ m ] 就是最大值。
如果是 ②
则会因为 f[ k ] == -inf,使得 ① 中的 Ⅱ 的情况不会发生,使得 f[ j ] 所需的体积一定是恰好等于 j。
所以,f[ m ] 不可能包含所有的情况,那么就必须比较所有 f[ j ] 得到最大值才是答案。
下面的代码中,给出了一个 ② 情况下,f[ m ] 不等于最大值的例子,可运行看看。
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #define inf 0x3f3f3f3f #define N 35 #define M 125 int v[N], w[N]; // 物品体积,物品价值 int f[M]; int max(int a, int b) { return a > b ? a : b; } int main(void) { /* 测试案例: 2 2 1 101 2 100 */ int n, m; // 物品数,背包体积 while (scanf("%d%d", &n, &m) != EOF) { for (int i = 1; i <= m; i++) f[i] = -inf; for (int i = 1; i <= n; i++) scanf("%d%d", &v[i], &w[i]); for (int i = 1; i <= n; i++) for (int j = m; j >= v[i]; j--) f[j] = max(f[j], f[j - v[i]] + w[i]); for (int i = 1; i <= m; i++) printf("%d ", f[i]); puts(""); } return 0; }
对于二维的代码:
初始化只有一种情况,即
f[ 0 ][ j ] == 0,对应一维的 f[ j ] == 0。
因为二维的所有值都是由 f[ 0 ][ j ] 推导而来的,所以 f[ i ][ j ] 代表的是:
只考虑到前 i 个物品,且总体积恰好等于 j 的情况下,背包的最大价值。
所以,最后必须比较所有 f[ n ][ j ] 得到最大值才是答案。
4,例题:
链接:
http://poj.org/problem?id=3624
二维代码:内存超限
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> #define N 3500 #define M 12880+5 int f[N][M]; int v[N], w[N]; // 物品体积,物品价值 int max(int a, int b) { return a > b ? a : b; } int main(void) { int n, m; // 物品数,背包体积 while (scanf("%d%d", &n, &m) != EOF) { memset(f, 0, sizeof(f)); for (int i = 1; i <= n; i++) scanf("%d%d", &v[i], &w[i]); for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { f[i][j] = f[i - 1][j]; // 默认不选 if (v[i] <= j) // 如果可以选,即物品体积 <= 背包体积 f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]); } } int rs = 0; for (int i = 1; i <= m; i++) rs = max(rs, f[n][i]); printf("%d\n", rs); } return 0; } /* 4 6 1 4 2 6 3 12 2 7 答案:23 */
一维代码
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> #define N 3500 #define M 12880+5 int v[N], w[N]; // 物品体积,物品价值 int f[M]; int max(int a, int b) { return a > b ? a : b; } int main(void) { int n, m; // 物品数,背包体积 while (scanf("%d%d", &n, &m) != EOF) { memset(f, 0, sizeof(f)); for (int i = 1; i <= n; i++) scanf("%d%d", &v[i], &w[i]); for (int i = 1; i <= n; i++) for (int j = m; j >= v[i]; j--) f[j] = max(f[j], f[j - v[i]] + w[i]); printf("%d\n", f[m]); } return 0; } /* 4 6 1 4 2 6 3 12 2 7 答案:23 */
二,完全背包问题
三,多重背包问题
四,混合背包问题
五,二维费用的背包问题
六,分组背包问题
七,背包问题求方案数
八,求背包问题的方案
九,有依赖的背包问题
========== ========= ======== ====== ====== ===== ==== === == =
Do you think, because I am poor, obscure, plain, and little,I am soulless and heartless?
You think wrong!
I have as much soul as you, and full as much heart!
And if God had gifted me with some beauty and much wealth, I should have made it as hard for you to leave me, as it is now for me to leave you.
I am not talking to you now through the medium of custom, conventionalities, nor even of mortal flesh;
it is my spirit that addresses your spirit,just as if both had passed through the grave, and we stood at God's feet, equal, -- as we are!
—— —— Jane Eyre