[动态规划 01背包详解]洛谷P2871 [USACO07DEC]手链Charm Bracelet

第一篇博客 请多关照
题目链接:https://www.luogu.org/problemnew/show/P2871

题目描述
有N件物品和一个容量为V的背包。第i件物品的重量是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。

输入格式:
第一行:物品个数N和背包大小M
第二行至第N+1行:第i个物品的重量C[i]和价值W[i]
输出格式:
输出一行最大价值。
样例:
输入:
4 6
1 4
2 6
3 12
2 7
输出:
23

分析这是一道标准的动态规划题——01背包。
首先01背包01的意思在物品中,一件物品只有两种状态——选与不选
那么我们可以设一个二维数组 f[m][n];
解释m与n的关系 m指按顺序选到第m件,n指当背包容量为n时的最大值;
通过题意我们设置两个一维数组 c[i]表示第i件物品的重量(所占位置/代价等等),w[i]指选了这件物品后所获得的价值(回报/分数等等)。
那么我们通过一件物品只有被选与不被选两种推一下DP方程:

当这件物品被选时
可知 f[m][n]=f[m-1][n-c[m]]+w[m]
(当然这个物品需要足够的空间 可以看后面代码)
解释
当选到第m件时必定与这前一件的最大值有关 因为选了这第m件物品 那必定要从n-c[m]推来 那你选了第m件 必定要加上价值即w[m]

当这件物品不被选时
可知 f[m][n]=f[m-1][n]
解释
同样的 当选到第m件时必定与这件物品的前一件有关 所以从m-1推来 因为不被选 所以不占体积 所以可以不用考虑n

通过上面两情况,又因为我们的定义 f[m][n]应为最大值 那么f[m][n]应为上面两种情况的最大值
即DP方程: f[m][n]=max(f[m-1][n-c[m]+w[m],f[m-1][n])
那么关于这道最后只要输出f[总件数][总体积]不必多说
代码实现:

#include<iostream>
#include<cstdio>
using namespace std;
long long f[10000][10000],c[100000],w[100000],s,v;
int main()
{
	cin>>s>>v;
	for(long long i=1;i<=s;i++)
	cin>>c[i]>>w[i];
	for(long long m=1;m<=s;m++)
	for(long long n=v;n>0;n--)//为了保证一定能装下这件物品 
	{ 
	if(n>=c[m]) //如果能装下 
	f[m][n]=max(f[m-1][n],f[m-1][n-c[m]]+w[m]);
	else//不能装下的话必定不能选
	f[m][n]=f[m-1][n]; 
	} 
	cout<<f[s][v];
	return 0;
}
 

重点:状态压缩

我们通过上面的解释可以了解到一点——
f[m][n]无论怎么选都必定于f[m-1][n或n-c[m]]的值有关(划重点)
那我们是不是可以将f[m][n]这个数组状态压缩到f[n]一维呢?
答案是可以的
在原本的二维DP中
我们运用双重for来更改数组的数值
那么必定一个数组会被分为两部分 一部分为修改好的f[n](即f[m][n])
另一部分为上个for修改的 而这个for还没修改到的(即f[m-1][n])
那么我们修改f[n]时只要能调动这次for还没修改到的 就可以实现二维DP的压缩
那么我们在原本二维DP思考
修改f[m][n]所调动的f[m-1][n或n-c[m]]容量(即它们各自对应的n)是比f[m][n]小的
那我们可以优先从容量大的修改起
那么就可以保证在修改f[n]时所调动f[n或n-c[m]]是还没修改的
有些难理解,自己结合题目多思考
放上一维压缩后的代码

#include<iostream>
#include<cstdio>
using namespace std;
long long f[1000000],c[100000],w[100000],s,v;
int main()
{
	cin>>s>>v;
	for(long long i=1;i<=s;i++)
	cin>>c[i]>>w[i];
	for(long long m=1;m<=s;m++)
	for(long long n=v;n>0;n--) //重点理解 
	{ 
	if(n>=c[m]) //如果能装下 
	f[n]=max(f[n],f[n-c[m]]+w[m]);
	else//不能的话必定不能选
	f[n]=f[n]; 
	} 
	cout<<f[v];
	return 0;
}

就解释到这里
第一篇博客 感谢支持

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值