01背包
特点:每个物品最多仅能用一次。
问题描述:从 N 个物品里选择总体积不超过 V 的物品的最大价值。
DP 和暴搜一样会枚举所有方案,当时 DP 的方式更省时。
背包问题 实际上是 组合问题(选择模型) 的一种,所以当看到组合问题时,就要考虑是不是背包问题了。
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 ii 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi, wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000输入样例
4 5 1 2 2 4 3 4 4 5
输出样例:
8
01 DP 问题的思考方式
从两个方向考虑,第一个是 状态表示f(i, j),第二个是状态计算。
状态表示:用几个未知数来表示 状态。分为两个分支,集合与属性。集合在01背包中就是所有选法的集合。选法满足两个条件:1. 只从前 i 个物品中选;2. 总体积小于等于 j。属性(一个值)是我们存的数 f() 究竟代表着什么,一般为 max、min、选法的数量。总之,01 背包状态表示就f(i, j) 前 i 个物品中选,体积不超过 j 的所有选法中的最大价值。
状态计算:如何一步步把每一个状态算出来。表示集合的划分。集合划分的原则为不重、不漏。
我们把每一步f(i, j)的集合划分成两类,不包含第 i 个物品的 选法集合的最大价值和包含第 i 个物品的选法中的最大价值。不含 i 的最大价值就是 f(i - 1, j)。包含 i 的最大价值就是 f(i - 1, j - Vi) + Wi
(可能是空集,当背包装不下时),所以我们得到每一步状态的计算公式 f(i, j) = Max(f(i - 1, j), f(i - 1, j - Vi) + Wi)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tpyo0LvG-1606481343707)(./DP.assets/image-20200427010819224.png)]
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int n, m; // 表示从n个物品中取体积不超过m的最大价值
int v[N], w[N];
int main()
{
cin >> n >> m;//物品数量和背包体积
for (int i = 1; i <= n; ++i) { // 存每个物品的体积和价值
cin >> v[i] >> w[i];
}
for (int i = 1; i <= n; ++i) { // f[0][0~m] 已经被初始化为 0
for (int j = 0; j <= m; ++j) { // 从前i个物品中取总体积小于等于j的选法的最大价值
f[i][j] = f[i - 1][j];
if (j >= v[i]) // 背包体积必须能装得下第i个物品
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
cout << f[n][m] << endl;
}
由于在维护世界和平的事务中做出巨大贡献,Dzx 被赠予糖果公司2010年5月23日当天无限量糖果免费优惠券。
在这一天,Dzx 可以从糖果公司的 N 件产品中任意选择若干件带回家享用。
糖果公司的 N 件产品每件都包含数量不同的糖果。
Dzx 希望他选择的产品包含的糖果总数是 K 的整数倍,这样他才能平均地将糖果分给帮助他维护世界和平的伙伴们。
当然,在满足这一条件的基础上,糖果总数越多越好。
Dzx 最多能带走多少糖果呢?
注意:Dzx只能将糖果公司的产品整件带走。
输入格式
第一行包含两个整数 N 和 K。
以下 N 行每行 11 个整数,表示糖果公司该件产品中包含的糖果数目,不超过 1000000。
输出格式
符合要求的最多能达到的糖果总数,如果不能达到 K 的倍数这一要求,输出 00。
数据范围
1≤N≤100,
1≤K≤100,输入样例:
5 7 1 2 3 4 5
输出样例:
14
样例解释
Dzx的选择是 2+3+4+5=14,这样糖果总数是 7 的倍数,并且是总数最多的选择。
究竟有几维状态量?题目重点信息是余数要为 K,有 N 袋糖果。因此,我们可以使用二维状态 f(i, j)。
状态表示:f(i, j) ,其中 i 表示选了前 i 件糖果袋,j 表示糖果总和的余数。
属性:最大值
状态计算:将集合 f(i, j) 划分成 “第 i 袋糖果选中” 和 “第 i 袋糖果不选”
第 i 袋物品不选,就是则 f(i, j) = f(i - 1, j)。
第 i 袋物品选,就是 f(i, j) = f(i - 1, (j - wi) % k) + wi
令c = a + b,若 (a + b) % K = j,则 c 拿去 b 后的余数为 (j - b) % K。
01 DP 优化
属于比较难的范畴,一般都是对写好的代码公式进行等价变形。所以**一开始写代码先不考虑优化。**滚动数组常常用在 DP 中进行优化空间。滚动数组本身就是用来优化数组空间的。
在 01 背包问题中我们发现 ,f(i, j) = Max(f(i - 1, j), f(i - 1, j - Vi) + Wi)
当前的 (i, j)一定是由之前的 状态决定的,并且我们只需要最后的状态 i = n,j = m作为结果,所以f 为滚动数组。所以我们可以用一维数组来对 01 背包进行优化。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N];
int n, m; // 表示从n个物品中取体积不超过m的最大价值
int v[N], w[N];
int main()
{
cin >> n >> m;//物品数量和背包体积
for (int i = 1; i <= n; ++i) { // 存每个物品的体积和价值
cin >> v[i] >> w[i];
}
for (int i = 1; i <= n; ++i) { // f[0][0~m] 已经被初始化为 0
for (int j = m; j >= v[i]; --j) { // 从前i个物品中取总体积小于等于j的选法的最大价值
f[j] = max(f[j], f[j - v[i]] + w[i]);// 背包体积必须能装得下第i个物品
}
}
cout << f[m] << endl;
}