leetcode补充学习之DP-01背包问题

01背包问题

写在前面:
最近越来越忙了,主要原因是真的太菜了,做个题需要好久,这两天题涉及还蛮复杂,昨天是DFS与BFS搜索,我打算等有时间复习一下再一并写。
结果今天居然来了个DP,好家伙 又不会,只能一步一步来了。
今天的题是完全背包问题,后面再说,这里只记录01背包问题。主要学习状态转移函数等。

废话少说 直接放题:

01背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000

样例输入

4 5
1 2
2 4
3 4
4 5

样例输出

8

其实这道题的关键我已经标黑了,就是每个物品只能拿一个,不能重复拿。
主要关键就是这个F[i][j],它代表着只看前i个物品 总体积是j的情况下 总最大价值

//01背包问题-二维解法 
//F[i][j] 表示只看前i个物品 总体积是j的情况下 总最大价值

#include<iostream>
using namespace std;
int arrW[1010];
int arrV[1010];
int F[1010][1010]; //状态转移 
int main()
{
	int N,V;
	cin>>N>>V; //N个物品 V体积的背包 
	for(int i=1;i<=N;i++)
	cin>>arrV[i]>>arrW[i]; //读入体积与价值
	 
	for(int i =1;i<=N;i++)
	for(int j=0;j<=V;j++)
	{
		F[i][j]=F[i-1][j]; //不拿第i个物品
		if(j>=arrV[i])
		 F[i][j]=max(F[i][j],F[i-1][j-arrV[i]]+arrW[i]);  //拿了第i个物品
	} 
	int res=0;
	for(int i =0;i<=V;i++)
	res=max(res,F[N][i]);
	cout<<res<<endl;
	return 0;
}

这里直接贴入样例输入,打印F[i][j]看看是什么东西,简单明了。
第0行 第0列 全是0代表我们没有空间或者没有物品的情况下什么都没有自然就是0
当有1个空间 1个物品时 就是2 代表我们拿了第一个物品
当有2个空间 2个物品时 就是4 代表拿的第二个物品

//F[i][j] 表示只看前i个物品 总体积是j的情况下 总最大价值
F[i][j]=F[i-1][j]; //不拿第i个物品
F[i][j]=max(F[i][j],F[i-1][j-arrV[i]]+arrW[i]);//拿

再看这代码就明白了 如果要拿第i个物品
就应该满足在i之前的空间足够放i物品情况下的最大价值来拿
例如目前为3空间 我可以拿两个物品:第二个物品它是2空间,4价值
那么就要找只有第一种物品的情况下空间为3-2=1此时的最大价值也就是2.
所以拿了之后的总价值是4+2=2。
不拿总价值就是2。
在这里插入图片描述
其实理解这个并不难,实际卡住我的是所谓的二维数组优化为一维 优化空间复杂度。
也就是下面的代码

//01背包问题-一维解法 
#include<iostream>
using namespace std;
int arrW[1010];
int arrV[1010];
int F[1010]; //F[i]表示体积为i的情况下最大价值 
int main()
{
	int N,V;
	cin>>N>>V; //N个物品 V体积的背包 
	for(int i=1;i<=N;i++)
	cin>>arrV[i]>>arrW[i]; //读入体积与价值
	 //推f[j]时f[j-arrV[i]]保存的是状态f[i-1][j-arrV[i]]的值
	for(int i =1;i<=N;i++)
		for(int j=V;j>=arrV[i];j--)
		{
			F[j]=max(F[j],F[j-arrV[i]]+arrW[i]);
			cout<<"j="<<j<<" F[j]="<<F[j]<<endl;
		}
	int res=0;
	for(int i =0;i<=V;i++)
	res=max(res,F[i]);
	cout<<res<<endl;
	return 0;
}

难点就在于下面这里
为什么第二个循环是逆着算的
其实可以这么来解释:如果采用顺序循环
当计算F[j]的时候右边需要计算F[j-arrV[i]]
例如:当我们算F[5]时第一次取物品时 它就需要计算 F[4],但F[4]在上一个循环已经计算了
它有可能已经拿了第i个物品了

//逆序
	for(int i =1;i<=N;i++)
		for(int j=V;j>=arrV[i];j--)
		F[j]=max(F[j],F[j-arrV[i]]+arrW[i]);
//顺序
	for(int i =1;i<=N;i++)
	for(int j=1;j<=V;j++)
		{
			F[j]=F[j];
			if(j>=arrV[i])
			F[j]=max(F[j],F[j-arrV[i]]+arrW[i]);
		}

这里同样输出两种情况的过程看一下就知道了
首先是逆序的输出
在这里插入图片描述
然后是顺序
在这里插入图片描述
对于这个样例输入
当可拿物品只有1个 空间为2,3,4,5的时候 都重复的拿了第一个物品
就是因为我们计算F[2]的时候F[1]它已经拿了第1个物品了,此时再取1显然已经非法了。
而逆着算就正好避免了这一问题。

4 5
//左边空间 右边价值
1 2
2 4
3 4
4 5

好了,本篇文章就到这里,下次继续 完全背包 问题

需要提交代码测试的移步下面的网址:
在线判题网站: 01背包问题.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值