PTA:真实的背包故事[动态规划]
PTA:真实的背包故事[动态规划]
题目
0-1背包问题是经典的动态规划问题,这个问题大多用这样的故事开场:一个小偷溜进了一家博物馆,博物馆里排列着N件珍稀古董,每件古董都有重量和价值,而小偷带来的背包有重量限制W。因此,小偷的目的就是要选择部分古董,使其总重量不超过W且总价值最大。这故事听上去就像小偷在逛超市一样能轻松自如地挑选,而真实的情况是小偷提心吊胆,尤其是每拿下一件古董,随时都有触动警报的危险,所以小偷想尽可能少带几件古董立马跑路,但他的职业“素养”又不允许他不把背包装满。你能帮他解决这个困境吗?
输入格式:
第一行中给出2个整数值N和W, 其中1<N≤100表示古董的数量,1<W≤2000表示背包的重量限制。 接下来N行数据,每行两个正整数。第i行(1≤i≤N)的整数vi和wi分别表示第i件古董的价值和重量。vi和wi的值均不超过2000。
输出格式:
在一行中输出两个整数值,用空格分开。第一个整数表示装背包能获得的最大总价值,第二个整数表示在获得最大价值的条件下装入背包里的古董的最少数量。
输入样例:
6 6
1 1
2 2
3 3
4 4
5 5
6 6
输出样例:
在这里给出相应的输出。例如:
6 1
思路
这个题目第一眼看上去就知道是0-1背包的动态规划问题,但是仔细看看还是和平常的0-1背包问题不太一样,这个题目还要求求出背包中要放置的最小物品数,就当前我在网上搜索到的根据最优矩阵倒退物品的方法都只能求出一种方案,不能求出最小的方案数,这个方案数也是这个问题的一个小难点。废话不多说,看代码:
AC代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int N, W, vi[2005], wi[2005], dpv[2005], dpn[2005];
//dpv是用来存放最大价值的滚动数组
//dpn是用来存放最小数目的滚动数组
int min(int a, int b) {
return a>b?b:a;
}
int main(void)
{
scanf("%d%d", &N, &W);
memset(dpv, 0, sizeof(dpv));
memset(dpn, 1, sizeof(dpn));//函数将每一个二进制位都置为1,效果等同于将数组中每个值置为无穷大(int所能表示的最大正数)
dpn[0] = 0;//填满容量为0的背包需要的物品数目为0
for(int i = 1; i <= N; ++i)
scanf("%d%d", &vi[i], &wi[i]);
for(int i = 1; i <= N; ++i) {
for(int j = W; j >= 1; --j) {
if(j >= wi[i]) { //能放下当前物品
if(dpv[j-wi[i]]+vi[i] >= dpv[j]) { //决定是否拿取当前物品
dpv[j] = dpv[j-wi[i]] + vi[i];//状态更新
dpn[j] = min(dpn[j], dpn[j-wi[i]]+1);//状态更新
}
}
}
#ifdef DEBUG
for(int i = 0; i <= W; ++i)
printf("%d ",dpn[i]);
printf("\n");
#endif
}
printf("%d %d\n", dpv[W], dpn[W]);
return 0;
}
改!!!
感谢来自CSDN评论区@qq_54770293的指正,最近又学习了一下背包问题,总算写出了这一版的代码,但是由于之前的题目集已经关闭交不了了,我不知道这个结果对不对,希望有hxd能在评论区给我反馈一下😅
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int vi[2005], wi[2005];
int dp[2005][2005];
int N, W;
int ans = 0;
int max(int a, int b)
{
return a > b ? a:b;
}
int main(void)
{
scanf("%d%d", &N, &W);
int mW = W;
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= N; ++i)
scanf("%d%d", &vi[i], &wi[i]);
for(int i = 1; i <= N; ++i) {
for(int j = 1; j <= W; ++j) {
if(j < wi[i])
dp[i][j] = dp[i-1][j];
else
dp[i][j] = max(dp[i-1][j], dp[i-1][j-wi[i]]+vi[i]);
}
}
int j = W; // 新增代码
for(int i = N; i > 0; --i) {
if(dp[i][j] > dp[i-1][j]) {
++ans;
j -= wi[i];
}
}
printf("%d %d\n", dp[N][W], ans);
return 0;
}
可以看到上面的代码和0-1背包的基本代码是一样的,多出来的一点就是用来求解物品数量的部分,这里详细解释一下:
int j = W; // 新增代码
for(int i = N; i > 0; --i) {
if(dp[i][j] > dp[i-1][j]) {
++ans;
j -= wi[i];
}
}
我们现在求解出了最优答案的矩阵,这个矩阵里就存放着我们选择物品的信息,我们要做的就是从这个矩阵里把它发掘出来。在求解dp数组的过程中什么情况下dp[i][j]
会区别于dp[i-1][j]
呢?当然是第i
个物品被选择了,那么反过来说,如果dp[i][j]
不同于dp[i-1][j]
,那么我们就可以说第i
个物品被选择了。因此我们只需要遍历这个答案矩阵,从矩阵的最右下角开始遍历每一个选择物品的行,如果相比上一行在相同的体积占用下价值更高了,则占用物品计数量加一(++ans;
)。当遍历完这个矩阵就得出用了几个物品了,而且这个数目一定是占用量最小的,这个思考一下就可以明白。
用这个思想还可以知道具体选择了哪几个物品,有需要的读者可以自己探索一下。
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现