01背包 2016.5.1
来源:互联网 发布:华三交换机ip和mac绑定 编辑:程序博客网 时间:2024/05/16 16:02
参考《背包九讲》
一、题目
有 N 件物品和一个容量为 V 的背包
放入第 i 件物品耗费的空间是 Ci,得到的价值是 Wi
求解将哪些物品装入背包可使价值总和最大
二、基本思路
特点:
每种物品仅有一件,可以选择放或不放
用子问题定义状态:
dp[i, V] 表示前 i 件物品恰放入一个容量为 v 的背包可以获得的最大价值
则其状态转移方程便是:
dp[i, V] = max{dp[i − 1, V], dp[i − 1, V − Ci] + Wi}
那么就可以转化为一个只和前 i − 1 件物品相关的问题
如果不放第 i 件物品,那么问题就转化为“ 前 i − 1 件物品放入容量为 V 的背包中 ”,价值为 dp[i − 1, V]
如果放第i件物品,那么问题就转化为“前 i − 1 件物品放入剩下的容量为 V − Ci 的背包中”
此时能获得的最大价值就是 dp[i − 1, V − Ci] 再加上通过放入第 i 件物品获得的价值 Wi
POJ 3624 Charm Bracelet
//时间和空间复杂度均为O(V * N)
//Result:MLE
#include <iostream>#include <cstdio>#include <cstring>using namespace std;const int maxn = 3410;int W[maxn];int D[maxn];int dp[maxn][12890];int main(){// freopen("in.txt", "r", stdin); int N, M; scanf("%d%d", &N, &M); for (int i=1; i<=N; ++i) { scanf("%d%d", &W[i], &D[i]); } memset(dp, 0, sizeof(dp)); for (int i=1; i<=N; ++i) { for (int j=1; j<W[i]; ++j) { dp[i][j] = dp[i-1][j]; } for (int j=W[i]; j<=M; ++j) { dp[i][j] = max(dp[i-1][j-W[i]] + D[i], dp[i-1][j]); } } printf("%d\n", dp[N][M]); return 0;}
三、优化空间复杂度
以上方法的时间和空间复杂度均为O(V * N),其中时间复杂度应该已经不能再优化了,但空间复杂度却可以优化到O(V)
上面的基本思路:有一个主循环 i = 1..N,每次算出来二维数组 dp[i, 0..V ] 的所有值
那么,如果只用一个数组 dp[0..V ],能不能保证第 i 次循环结束后 dp[V] 中表示的就是我们定义的状态 dp[i, V] 呢?
dp[i, v] 是由 dp[i − 1, v] 和 dp[i − 1, v − Ci] 两个子问题递推而来
能否保证在推 dp[i, v] 时(也即在第 i 次主循环中推 dp[v] 时)能够取用 dp[i − 1, v] 和 dp[i − 1, v − Ci] 的值呢?
事实上,这要求在每次主循环中我们以 v = V..0 的递减顺序计算dp[v]
这样才能保证推 dp[v]时 dp[v − Ci] 保存的是状态 dp[i − 1,v − Ci] 的值
POJ 3624 Charm Bracelet
#include <iostream>#include <cstdio>#include <cstring>using namespace std;const int maxn = 3410;int W[maxn];int D[maxn];int dp[12890];int main(){// freopen("in.txt", "r", stdin); int N, M; scanf("%d%d", &N, &M); for (int i=1; i<=N; ++i) { scanf("%d%d", &W[i], &D[i]); } memset(dp, 0, sizeof(dp)); for (int i=1; i<=N; ++i) { for (int j=M; j>=W[i]; --j) { if (dp[j-W[i]] + D[i] > dp[j]) { dp[j] = dp[j-W[i]] + D[i]; } } } printf("%d\n", dp[M]); return 0;}
四、初始化的细节问题
求最优解的背包问题题目中,事实上有两种不太相同的问法
有的题目要求 “恰好装满背包” 时的最优解
有的题目则并没有要求必须把背包装满一种区别这两种问法的实现方法是在初始化的时候有所不同
如果是第一种问法,要求恰好装满背包
那么在初始化时除了 dp[0] 为 0,其它 dp[1..V ] 均设为 −∞,这样就可以保证最终得到的 dp[V] 是一种恰好装满背包的最优解
如果并没有要求必须把背包装满,而是只希望价格尽量大
初始化时应该将 dp[0..V ] 全部设为 0
这是为什么呢?可以这样理解:
初始化的F 数组事实上就是在没有任何物品可以放入背包时的合法状态
如果要求背包恰好装满,那么此时只有容量为 0 的背包可以在什么也不装且价值为 0 的情况下被“恰好装满”
其它容量的背包均没有合法的解,属于未定义的状态,应该被赋值为 -∞ 了
如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”
这个解的价值为 0,所以初始时状态的值也就全部为 0 了
五、小结
01背包问题是最基本的背包问题
它包含了背包问题中设计状态、方程的最基本思想
另外,别的类型的背包问题往往也可以转换成01背包问题求解
故一定要仔细体会上面基本思路的得出方法,状态转移方程的意义,以及空间复杂度怎样被优化
HDU 2602 Bone Collector
#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>#include <queue>using namespace std;typedef long long LL;const int maxn = 100 + 10;double Pj[maxn];int Mj[maxn];double dp[10010];int main(){#ifdef __AiR_H freopen("in.txt", "r", stdin);#endif // __AiR_H int T; while (scanf("%d", &T) != EOF) { while (T--) { double P; int N; int sum = 0; scanf("%lf%d", &P, &N); for (int i = 1; i <= N; ++i) { scanf("%d%lf", &Mj[i], &Pj[i]); sum += Mj[i]; } memset(dp, 0, sizeof(dp)); dp[0] = 1; for (int i = 1; i <= N; ++i) { for (int j = sum; j >= Mj[i]; --j) { dp[j] = max(dp[j], dp[j - Mj[i]] * (1 - Pj[i])); } } for (int i = sum; i >= 0; --i) { if (dp[i] >= (1-P)){ printf("%d\n", i); break; } } } } return 0;}
HDU 2546 饭卡
解题思路:
最后买最贵的菜(可能有多个)
然后就是求 V = m-5 且不考虑第一个最贵饭菜的01背包问题
#include <iostream>#include <cstdio>#include <cstring>using namespace std;const int maxn = 1000 + 10;int price[maxn];int dp[1010];int main(){#ifdef __AiR_H freopen("in.txt", "r", stdin);#endif // __AiR_H int n; while (scanf("%d", &n) != EOF && n != 0) { int Max; int t; for (int i = 1; i <= n; ++i) { scanf("%d", &price[i]); if (i == 1) { Max = price[i]; t = 1; } else if (price[i] > Max) { Max = price[i]; t = i; } } int m; scanf("%d", &m); if (m < 5) { printf("%d\n", m); } else { memset(dp, 0, sizeof(dp)); for (int i = 1; i <= n; ++i) { if (i != t) { for (int j = m-5; j >= price[i]; --j) { dp[j] = max(dp[j], dp[j-price[i]] + price[i]); } } } printf("%d\n", m-dp[m-5]-Max); } } return 0;}
HDU 1171 Big Event in HDU
转化为 V = halves 的01背包问题
#include <iostream>#include <cstdio>#include <cstring>using namespace std;const int maxn = 50 + 10;int V[maxn];int M[maxn];int dp[130000];int main(){#ifdef __AiR_H freopen("in.txt", "r", stdin);#endif // __AiR_H int N; while (scanf("%d", &N) != EOF && N >= 0) { int sum = 0; for (int i = 1; i <= N; ++i) { scanf("%d%d", &V[i], &M[i]); sum += (V[i] * M[i]); } int halves = sum / 2; memset(dp, 0, sizeof(dp)); for (int i = 1; i <= N; ++i) { for (int j = 0; j < M[i]; ++j) { for (int k = halves; k >= V[i]; --k) { dp[k] = max(dp[k], dp[k-V[i]] + V[i]); } } } printf("%d %d\n", sum-dp[halves], dp[halves]); } return 0;}
- 01背包 2016.5.1
- 背包问题1:01背包
- 背包问题《1》01背包
- 背包问题(1)——01背包、完全背包、多重背包、混合三种背包问题
- DP总结(1) 01背包 完全背包 多重背包
- 背包问题1(01背包)
- 1poj3624(01背包)
- 题目1 : 01背包
- 算法-01背包1
- 背包九讲系列1——01背包、完全背包、多重背包
- 【背包专题】01背包
- 01背包,完全背包
- 01背包 完全背包
- 01背包/完全背包
- 01背包,完全背包
- 背包问题---01背包
- 背包入门--01背包
- 【背包专题】01背包
- spring 配置文件详解-个人学习笔记
- HDU 3639Hawk-and-Chicken 强连通分量分解 + dfs
- 算法竞赛入门第二章2-2
- 使用KLEE Docker image进行程序分析
- java并发之ConcurrentHashMap
- 01背包 2016.5.1
- myapps快速开发平台常用知识点
- serlet和JSP之间值传递,一些基础的java,
- 正整数的打印
- 给定入栈顺序,输出所有可能出栈情况及所有情况的总数
- 数据科学家的能力需求
- MyEclipse2014 如何破解
- 项目组长说java学习
- node.js之this的困惑