01背包

01背包

特点:每个物品最多仅能用一次。

问题描述:从 N 个物品里选择总体积不超过 V 的物品的最大价值

DP 和暴搜一样会枚举所有方案,当时 DP 的方式更省时。

背包问题 实际上是 组合问题(选择模型) 的一种,所以当看到组合问题时,就要考虑是不是背包问题了。

01背包问题

有 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 袋糖果不选”image-20201016231927826

第 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;
 }

参考:AcWing

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值