11.7 背包问题
11.7 背包问题
http://codeup.hustoj.com/contest.php?cid=100000631
A 装箱问题

题目解析
这道题其实就是01背包,01背包的二维状态转移方程:
令dp[i][v] 表示前i件物品恰好装入容量为v的背包中所能获得的最大价值
dp[i][v] = max{ dp[i-1][v] , dp[i-1][v-w[i]]+c[i]} (1<=i<=n, w[i]<=v<=V) ,边界:dp[0][v]=0(0<=v<=V)
化简为一维状态转移方程:【要逆序遍历v】
dp[v] = max{ dp[v] , dp[v-w[i]]+c[i]} (w[i]<=v<=V),边界:dp[v]=0(0<=v<=V)
此题只有容量V和每个物品的体积,没有箱子的价值,所以这里也将箱子的体积作为箱子的价值,一维状态转移方程:
dp[i][v]表示前i个物品恰好装入容量为v的箱子中所能获得的最大体积
dp[v] = max{ dp[v] , dp[v-w[i]]+w[i]} (w[i]<=v<=V),边界:dp[v]=0(0<=v<=V)
最后遍历所有dp[v] (0<=v<=V) 找到最大的那个就是能获得的最大体积max_v
我现在觉得不用遍历所有的dp[v]。。。dp[V]就是答案,不明白为什么书上说要遍历所有的取最大值,我觉得dp[V]肯定是最大的
⚠️ 最后要求输出的是最小剩余空间,故为1-ans
代码
#include <cstdio>
#include <algorithm>
using namespace std;
#define maxV 20005 //体积上限
#define maxn 100 //物品上限
int main() {
int V, n;
int v[maxn];
int dp[maxV];
scanf("%d%d", &V, &n);
for (int i = 1; i <= n; i++) scanf("%d", &v[i]);
for (int i = 0; i <= V; i++) dp[i] = 0; //对应d[0][i]=0 前0间物品装i容量箱子的最大体积
for (int i = 1; i <= n; i++) {
for (int j = V; j >= v[i]; j--) {
dp[j] = max(dp[j], dp[j - v[i]] + v[i]);
}
}
//得到dp[n][j]前n件物品装j容量箱子的最大体积
int ans = 0;
for (int i = 0; i <= V; i++) {
if (dp[i] > ans) ans = dp[i];
}
printf("%d\n", V - ans);
return 0;
}
B 采药

题目解析
这道题就是原原本本的01背包:
T总共能够用来采药的时间——背包的容量
M山洞里的草药的数目——物品的种类
采摘某株草药的时间和这株草药的价值——每项物品的重量和价值
PS:我觉得最后可以直接输出dp[V],这就是答案,不用遍历后取最大值
代码
#include <cstdio>
#include <algorithm>
#define maxn 105 //最大物品数
#define maxv 1005 //V上限
using namespace std;
int main() {
int V, n;
int w[maxn], c[maxn], dp[maxv];
scanf("%d%d", &V, &n);
for (int i = 1; i <= n; i++) {
scanf("%d%d", &w[i], &c[i]);
}
for (int i = 0; i <= V; i++) dp[i] = 0; //初始化dp[0][i]为前0件物品装入容量为i背包中的最大价值
for (int i = 1; i <= n; i++) {
for (int v = V; v >= w[i]; v--) {
dp[v] = max(dp[v], dp[v - w[i]] + c[i]);
}
}
int ans = 0;
for (int i = 0; i <= V; i++) {
if (dp[i] > ans) ans = dp[i];
}
printf("%d\n", ans);
return 0;
}
C 货币系统 🌟🌟

知识回顾
这道题是恰好装满的完全背包问题
1️⃣ 01背包 🆚 完全背包
-
一维的状态转移方程相同:dp[v] = max{ dp[v] , dp[v-w[i]]+c[i]} (w[i]<=v<=V)
01背包要逆序遍历v,完全背包要顺序遍历v -
完全背包的二维状态方程与01背包不同,这就是为啥01背包是逆序遍历v,而完全背包是顺序遍历的原因
完全背包二维状态方程:
令dp[i][v] 表示前i件物品恰好装入容量为v的背包中所能获得的最大价值
dp[i][v] = max{ dp[i-1][v] , dp[i][v-w[i]]+c[i]} (1<=i<=n, w[i]<=v<=V) ,边界:dp[0][v]=0(0<=v<=V) -
所以完全背包化简为一维后,dp[v]是上一状态的dp[i-1][v], dp[v-w[i]]是此时状态的dp[i][v-w[i]],故顺序遍历
01背包中dp[v]、dp[v-w[i]]都是上一状态的,故逆序遍历
(否则遍历到dp[j]时dp[0~j-1]都是此时的状态,上一状态已经被覆盖)
2️⃣ 恰好装满 🆚 无需完全装满
- 要求恰好装满背包,那么在初始化时除 dp[0]为0其它f[1..V]均设为-∞,这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解
- 如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将 dp[0..V]全部设为0。
- Why?可以这样理解——
- 初始化的dp数组事实上就是前0件物品可以放入背包时的合法状态。
- 如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的情况下被“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是-∞了。
- 如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了
💡这个小技巧完全可以推广到其它类型的背包问题
题目解析
这道题是恰好装满的完全背包问题,每种货币可选多次,要组成货币面值为V
1️⃣ 设置dp[i][v] 表示前i种货币恰好组成面值v的方案,为👇两种方案数相加
- 当前不选第i种货币,则为前 i-1 种货币组成面值v的方案
- 当前选第i中货币,则为前i种货币组成面值 v-w[i] 的方案(w[i]存储第 i 种货币的面值)
dp[i][v] = dp[i-1][v] + dp[i][v-w[i]] (1<=i<=n, w[i]<=v<=V)
2️⃣ 因为最终要恰好组成面值V,所以是“恰好完全装满”问题,这时dp[0][0]用前0种货币恰好组成面值0的方案数目为1
(只有0面值能被前0种货币构成这一种合法解)其他dp[0][v]=0,表示都没有合法解,方案数是0
3️⃣ 化简为一维动态转移方程:dp[v] = dp[v] + dp[v-w[i]] (w[i]<=v<=V)
因为是完全背包,所以顺序遍历 v,边界dp[v]=0 (0<=v<=V), dp[0]=1
代码
#include <cstdio>
#include <algorithm>
#define maxn 30
#define maxv 10005
typedef long long LL;
using namespace std;
int main() {
int n, V;
int w[maxn];
LL dp[maxv];
while (scanf("%d%d", &n, &V) != EOF) {
for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
fill(dp, dp + maxv, 0);
dp[0] = 1;
for (int i = 1; i <= n; i++) {
for (int v = w[i]; v <= V; v++) {
dp[v] = dp[v] + dp[v - w[i]];
}
}
printf("%lld\n", dp[V]);
}
return 0;
}
本文作者:Joey-Wang
本文链接:https://www.cnblogs.com/joey-wang/p/14541196.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步