1.小偷-01背包

        背包问题是动态规划的经典问题,是指在一个有容积或者重量限制的条件下放入物品,物品有重量,价值还有编号或者其他属性,要求不超过容量的前提下,使得背包内的物品价值最大,常见的背包类型有01背包,完全背包,分组背包,多重背包等。


        01背包:一共有n种物品,每种物品都有不同的重量和价值,每种物品只有一个,要不不放(0),要么放入(1),所谓01背包 



解题思路:

 1.每一种物品都有放或者不放的选择,随着背包容量的增多,存放的物品种类增加,每一个物品的增加都是在之前已经选择最大价值物品基础上的延伸,最优解包含最优子结构,无后效性体现在选择到当前物品,不去考虑后面的物品的价值,只关心在现有背包容量的基础上和现有的物品中选择最大值

2.设置状态:利用dp【i】【j】来表示前i个物品在容量为j的背包中的最大价值

3.状态转移:当面临第i种物品时,如果不放入背包(j<w[i])的话,那么此时的价值还是前i-1种商品的价值,即dp[i][j]=dp[i-1][j];当放入第i种物品时(j>=w[i]),那么必须意味着牺牲背包的空间,前i种商品的价值为dp[i-1][j-w[i]],因为此时第i种物品要占据w[i]的容量,然后再加上第i件商品的价值,所以状态转移方程为dp[i][j]=dp[i-1][j-w[i]]+v[i]

4.第三步如果放入背包后就一定是价值增多了么?不是,因为如果你放入这个物品,背包容量会减少,前i种商品可能会因为背包不足而扔掉,如果扔掉的商品的价值比放入的商品价值大的话,那么还不如不放,所以状态转移方程为dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]),选择较大值

5.初始化,当背包容量为0的时候,有再多的商品也放不进来,所以价值为0,当商品数为0的时候,背包再大也同样没有价值,即dp[i][0]=0,dp[0][j]=0;

6.根据递推公式填表,求目标值d[n][w],n为商品数量,w为背包容量


#include<bits/stdc++.h>
using namespace std;
int dp[1010][1010],w[1010],v[1010];
int main()
{
	int n,W;//表示物品数量和背包容量 
	cin>>n>>W;
	
	for(int i=1;i<=n;i++)
	cin>>w[i];//输入每件物品的重量 
	
	for(int i=1;i<=n;i++)
	cin>>v[i];//输入每件物品的价值 
	
	for(int i=1;i<=n;i++)//枚举每件商品 
	{
		for(int j=1;j<=W;j++)//枚举背包容量 
		{
			if(j<w[i])//如果放不下第i件商品 
			dp[i][j]=dp[i-1][j];//继承前i-1件商品的价值 
			else//如果可以放下 
			dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);//选择较大值 
		}
	}
	
	cout<<dp[n][W];//输出目标值 
	return 0;
}

拓展延伸:如何输出是哪些商品放入背包了呢?


1.由上述表格可以清楚的看到,如果将第i件物品没有放入背包,是继承i-1的最大价值,即此时的值和上一行同列的值是相等的,如果放入背包后,那么此时的价值是上一行第j-w[i]列的值+此时商品的值。

2.所以可以根据求得的目标值倒推回去,如果目标值和上一行相等,则该件商品,没有放入背包,打上标记,如果价值不相等,则该件商品放入了背包,何时放进去的呢?就是在上一行第j-w[i]列放入的,然后依次判断,直到i==0

3.根据标记数组输出放入背包商品的编号


​​​​代码实现


int temp=W;//设置书签 
	bool flag[n+1];//设置标记数组 
	for(int i=n;i>=1;i--)//从最后一行倒推 
	{
		if(dp[i][temp]==dp[i-1][temp])//如果此时价值和上一行价值相等 
		flag[i]=0;//说明该件商品没有放入 
		else//否则的话 
		{
			flag[i]=1;//背包中有这件商品 
			temp=temp-w[i];//书签跳转到第temp-w[i]列,此时加入的背包 
		}
	}
	for(int i=1;i<=n;i++)
	if(flag[i]==1)
	cout<<i<<" ";

空间优化:

1.在上述的填表过程中,时间和空间复杂度都是n*W的,如果遇到数据量大的时候,二维数组的大小受到空间限制,那么如何压缩空间呢?

2.在枚举每一个商品的时候,当背包容量放不下该商品的时候,都是直接继承上一行即dp[i-1][j],继承的时候是背包价值是不更新的,对于新加进来的物品,直接从j=w[i]来判断即可,因为此时的背包容量恰好能放下第i件商品,从此列到以后的值,才是真正做选择和决策的地方

3.所以利用一个一维数组来维护最大价值,每次从第W列倒推到W-w[i]列即可,为什么是倒推?因为一维数组是实时变化更新的值,为了不产生旧值还没利用完就被新值覆盖的情况,所以倒着推

4.那么状态转移方程也就变成了dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

5.输出dp【W】即为目标值,空间复杂度为O(W)


#include<bits/stdc++.h>
using namespace std;
int w[100010],v[100010],dp[100010];
int main()
{
	int n,W;
	cin>>n>>W;
	
	for(int i=1;i<=n;i++)
	cin>>w[i];
	
	for(int i=1;i<=n;i++)
	cin>>v[i];
	
	for(int i=1;i<=n;i++)//枚举每一个物品 
	{
		for(int j=W;j>=w[i];j--)//倒着推,直到恰好放下他的背包容量 
		{
			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//选择较大值 
		}
	}
	cout<<dp[W];
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值