完全背包问题详解
背包问题详解
2.完全背包
2.1引入
小明去文具店采购,他有 W 元预算,店里卖各种文具,每种的单价和实用价值都不一样,但每种文具库存无限,想买几个买几个。他该怎么买,才能让总实用价值最大?
物品价格:
笔记本:5元/本,价值8分
签字笔:3元/支,价值5分
橡皮:2元/块,价值3分
小明有20元预算,笔记本买4本最值吗?还是混着买?
只能选一次是01背包,而可以选多次就是完全背包
2.2完全背包与01背包的区别
| 01背包 | 完全背包 | |
|---|---|---|
| 每件物品 | 只能选择一次 | 可以选择无限次 |
| 遍历顺序(二维) | 正序 | 正序 |
| 遍历顺序(一维) | 倒叙 | 正序 |
2.3为什么使用动态规划?
如果要解决完全背包问题,我们会有一些选择:
| 方法 | 思路 | 时间复杂度 | 评价 |
|---|---|---|---|
| 暴力枚举 | 枚举每种物品选几个 | \(+∞种\) | 不可行× |
| 贪心 | 每次选"性价比"最高的 | \(O(n log n)\) | 不一定是最优× |
| 转化为01背包 | 把无限拆成有限个 | \(O(n × W × W/w)\) | 太慢× |
| 动态规划 | 记录子问题最优解 | \(O(n × W)\) | 最优√ |
2.4二维dp写法
2.4.1状态转移方程
\(dp[i][j]代表了只考虑前i种物品,容量为j时的最大价值\)
状态转移方程:
dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i])
- 选择第i种:dp[i-1][j]
- 不选第i种:dp[i][j-w[i]]+v[i]
注:状态转移方程中w[i]表示第i样物品的重量,v[i]表示第i样物品的价值
2.4.2二维代码
#include<bits/stdc++.h>//万能头文件
using namespace std;
int dp[1005][1005];
int main() {
int m,n;cin>>n>>m;//n代表物品数量,m代表背包大小
int w,c;
for(int i=1;i<=n;i++) {
cin>>w>>c;//w代表重量,c代表价值
for(int j=0;j<=m;j++) {//枚举各个背包大小时的最大价值
dp[i][j]=dp[i-1][j];//默认为上一次的结果
if(j>=w) {//如果当前背包大小不少于w
dp[i][j]=max(dp[i][j],dp[i][j-w]+c);//状态转移方程
}
}
}
cout<<dp[n][m];//输出答案
return 0;
}
2.5一维空间优化
2.5.1如何压缩成一维
二维状态转移第\(i\)行用到了:
-
上一行同列:\(dp[i-1][j]\)
-
当前行左侧:\(dp[i][j - w[i]]\)
所以压缩成一维后,需要从左往右遍历,因为 \(dp[j-w[i]]\)必须是当前行已经更新过的值。
2.5.2一维代码
#include<bits/stdc++.h>//万能头文件
using namespace std;
int dp[1005];//定义数组
int main() {
int m,n;cin>>n>>m;//
int w,c;//w为重量,c为价值
for(int i=1;i<=n;i++) {
cin>>w>>c;//输入
for(int j=0;j<=m;j++) {//正序遍历
if(j>=w) {//判断
dp[j]=max(dp[j],dp[j-w]+c);//取较大值
}
}
}
cout<<dp[m];//输出答案
return 0;
}
2.6例题
2.6.1 P1616 疯狂的采药
题目背景
此题为纪念 LiYuxiang 而生。
题目描述
LiYuxiang 是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同种类的草药,采每一种都需要一些时间,每一种也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是 LiYuxiang,你能完成这个任务吗?
此题和原题的不同点:
\(1\). 每种草药可以无限制地疯狂采摘。
\(2\). 药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!
输入格式
输入第一行有两个整数,分别代表总共能够用来采药的时间 \(t\) 和代表山洞里的草药的数目 \(m\)。
第 \(2\) 到第 \((m + 1)\) 行,每行两个整数,第 \((i + 1)\) 行的整数 \(a_i, b_i\) 分别表示采摘第 \(i\) 种草药的时间和该草药的价值。
输出格式
输出一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
输入输出样例 #1
输入 #1
70 3
71 100
69 1
1 2
输出 #1
140
说明/提示
【数据规模与约定】
- 对于 \(30\%\) 的数据,保证 \(m \le 10^3\)。
- 对于 \(100\%\) 的数据,保证 \(1 \leq m \le 10^4\),\(1 \leq t \leq 10^7\),且 \(1 \leq m \times t \leq 10^7\),\(1 \leq a_i, b_i \leq 10^4\)。
2.6.2解法
- 本题每种药都可以采无限次,所以我们可以想到完全背包
- 再结合本题的数据规模,我们可以发现用二维空间会超限,所以本题需要用一维dp
2.6.3代码
#include<bits/stdc++.h>//万能头文件
using namespace std;
#define ll long long
ll dp[10000005];//定义一维数组
int main() {
ll m,n;cin>>m>>n;//输入
ll w,c;
for(ll i=1;i<=n;i++) {
cin>>w>>c;//w为每种药材采摘时间,c为采摘总时间
for(ll j=0;j<=m;j++) {//正向遍历
if(j>=w) {//判断时间是否充足
dp[j]=max(dp[j],dp[j-w]+c);
}
}
}
cout<<dp[m];//输出结果
return 0;
}

浙公网安备 33010602011771号