01背包问题

对于闫氏DP分析法自己的理解

在这里插入图片描述
y总的这套分析我感觉很受用,以前学习的时候还是有一些迷糊,重点就是状态计算这里。

首先我们要清楚什么是01背包。

01背包:给你N个物品,每个物品有自己的体积和价值,你现在有一个体积为V的背包,如何在有限的背包里装到最大价值的物品。(每个物品只有一个)

首先我们会定义一个数组,这个数组的本质是一个集合,实质是这个集合的属性,也就是这个具体数组代表的质。在闫氏DP分析法中,状态表示就为我们很好的阐述了。
就对于当前01背包来讲,dp[i][j]代表的就是在前i个物品中选出体积不超过j的最大价值是多少。最终的答案肯定是dp[N][V]了。

然后重点就是状态如何进行计算,这也是DP问题的难点之一。我们首先要清楚状态计算的原则是不重不漏,要在这个基础上我们要进行状态计算。闫氏DP分析法也告诉我们了,状态计算本质就是集合如何划分,划分了过后如何去根据以往的集合求出我们当前的集合。要明白每一个dp[i][j]算出来的都是当前情况的最优解。

对于01背包来看我们就是将当前这个集合分为两个部分,第一个是不含i,就是在前i-1个物品中体积不超过j的最大价值(有可能我们选了i过后没有之前的价值大);第二个集合是一定要包含i这个物品,就是在前i个物品中体积不超过j但是一定要包含i的最大价值(如果我选了i可能会有更大的答案)。我认为集合的划分一定是从当前这种情况进行推演,然后在推广于每种情况看是否合理

第一个集合很好处理就是dp[i-1][j]就是了,之前就对于第二个集合很难去理解。第二个的思想其实就类似于曲线救国了。我们就假设我已经把第i个物品取了,那么随之我取的范围和还需要的体积就要发生变化了,准确的来说就是范围变小了。如果我一定要取第i个物品,那么再这基础上我是要在前i-1个物品中,体积不超过j-v[i]来找一个最大值,这样就能完美的阐述出第二个集合的表达方式了dp[i-1][j-v[i]]+w[i]。

例题 Acwing 2

链接: link

朴素版

#include<bits/stdc++.h>

#define IOS ios::sync_with_stdio(false);cin.tie(nullptr)
#define int long long
#define endl "\n"

using namespace std;

const int N = 1010;

int n, m;
int w[N], v[N];
int dp[N][N];

signed main()
{
	IOS;
	cin >> n >> m;
	for(int i = 1; i <= n; i ++) cin >> v[i] >> w[i];

	for(int i = 1; i <= n; i ++)
	{
		for(int j = 0; j <= m; j ++)
		{
			dp[i][j] = dp[i-1][j];//起初都是一样的
			if(j >= v[i]) dp[i][j] = max(dp[i][j], dp[i-1][j-v[i]]+w[i]);
		}
	}
	cout << dp[n][m] << endl;
	return 0;
}

优化

因为我们dp[i]这一层只有[i-1]这个当i等于3的时候只与2有关1是没有关系的,我们可以舍弃i这层,实质就是一维只与体积有关,比如dp[i-1][j], dp[i][j],其本质都是找到在体积不超过j的情况下我们能找到的最大值,起初都是一样的dp[i-1][j], dp[i][j]。我们就可以采用滚动数组挨着覆盖起初的dp[j], 意思就是我可以直接看当前这个dp[j],在以往的情况下我们的dp[j]就是最好的了我们现在所需要考虑的是选上i产生的影响,选上第i个物品过后和原来的最大值。然后当我们删掉第一维过后:

	dp[i][j] = dp[i-1][j]; --> dp[j] = dp[j] 没有必要了是一个恒等式
	if(j >= v[i]) dp[j] = max(dp[j], dp[j-v[i]]+w[i]);
	如果j < v[i]自动划过不管所以我们的第二重循环直接从v[i]开始
	for(int j = v[i]; j <= m; j ++)
	 dp[j] = max(dp[j], dp[j-v[i]]+w[i])
	但是我们原来后面是dp[i-1][j-v[i]]+w[i],如果我们这样修改这里的j-v[i]就是从i开始的不符合题意。
	如果我们倒退从m->v[i]呢这要以前的旧值就会保存下来就是i-1层的j-v[i]会被保存下来

链接: 优化link

#include<bits/stdc++.h>

#define IOS ios::sync_with_stdio(false);cin.tie(nullptr)
#define int long long
#define endl "\n"

using namespace std;

const int N = 1010;

int n, m;
int w[N], v[N];
int dp[N];

signed main()
{
	IOS;
	cin >> n >> m;
	for(int i = 1; i <= n; i ++) cin >> v[i] >> w[i];

	for(int i = 1; i <= n; i ++)
	{
		for(int j = m; j >= v[i]; j --)
		{
			dp[j] = max(dp[j], dp[j-v[i]]+w[i]);
		}
	}
	cout << dp[m] << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值