背包问题(01背包、完全背包、多重背包)

1、01背包问题

问题描述:

给定物体数量N,以及背包能够装下的最大重量V,对于物品i, 其重量为 weight[i],价值为value[i]。每种物品最多只能拿一次,求在不超过背包重量的前提下,能够拿到的物品的总价值的最大值是多少?

解决思路:

  • 1、确定dp数组下标的含义
  • 2、确定递推公式
  • 3、初始化dp数组
  • 4、确定遍历顺序

(1)确定dp数组下标的含义

  • 用二维dp[i][j]来表示,在前i件物品中取不超过重量为j的最大价值

(2)确定递推公式

  • 当第i件物品的重量大于j时,即weight[i] > j时,此时不能拿第i件物品。故dp[i][j] = dp[i-1][j]
  • 当第i件物品的重量小于等于j时,即weight[i] <= j时,此时可以拿也可以不拿。若拿第i件物品,则dp[i][j] = dp[i-1][j - weight[i]] + value[i],若不拿,则dp[i][j] = dp[i-1][j]。由于题目要求拿价值最大的,故在这两个当中取较大的那个。

递推公式如下:

在这里插入图片描述
(3)初始化dp数组

i = 0时,即只拿第一个物品时,如果j<weight[0],则dp[i][j] = 0;否则dp[0][j] = value[i]

初始化代码如下:

vector<vector<int>>dp(N, vector<int>(V+1)); //N为物品数量,V为背包能装的最大重量
for(int j = 0; j <= V; j++)
{
	if(j >= weight[0])
	{
		dp[0][i] = weight[0];
	}	
}

(4)确定遍历序列

先遍历物品数量,再遍历背包容量

代码如下:

//先遍历物品数量,再遍历背包容量
for(int i = 0; i < N; i++) //遍历物品数量
{
	for(int j = V; j >= 0; j++) //遍历背包容量
	{
		if(j >= weight[i])
		{
			dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]);
		}
		else
		{
			dp[i][j] = dp[i-1][j];
		}
	}
}

注意:在上述代码中,遍历背包容量时采用倒序是因为,每种物品只能拿一次。

具体原因:

如果遍历背包容量时采用正序,那么我们可以知道,i++之后,dp[i][j]又重复拿了之前拿过的物品。

完整代码如下:

#include<iostream>
#include<vector>
using namespace std;

int main()
{
	int N, V;
	cin >> N >> V;
	vector<int>weight(N);
	vector<int>value(N);
	for(int i = 0; i < N; i++)
	{
		cin >> weight[i] >> value[i];
	}
	vector<vector<int>>dp(N, vector<int>(V+1));
	//初始化dp数组
	for(int j = 0; j <= V; j++)
	{
		if(j >= weight[0])
		{
			dp[0][i] = weight[0];
		}	
	}
	//先遍历物品数量,再遍历背包容量
	for(int i = 0; i < N; i++) //遍历物品数量
	{
		for(int j = V; j >= 0; j--) //遍历背包容量
		{
			if(j >= weight[i])
			{
				dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]);
			}
			else
			{
				dp[i][j] = dp[i-1][j];
			}
		}
	}
	cout << dp[N-1][V] << endl;
	
	return 0;
}

优化:对于01背包问题,时间复杂度为O(VN),已经无法优化。但是空间复杂度可以从O(VN)优化为O(V)。

具体解决流程同上。

(1)确定dp数组下标含义

dp[j]表示背包容量为j能够装的最大价值

(2)确定递推公式

  • j < weight[i],则该物品不能拿,dp[j] = dp[j];
  • j >= weight[i],则可以选择拿该物体,或者不拿该物体
    若当前物品不拿,则dp[j] = dp[j];
    若当前物品拿的话,则dp[j] = dp[j-weight[i]] + value[i];

所以dp[i][j]递推公式如下:

在这里插入图片描述
(3)初始化dp数组

当背包容量为0时,可以拿的最大价值为0,所以初始化dp[0] = 0

(4)确定遍历顺序

必须先遍历物品种类,再遍历背包容量,且背包容量遍历顺序为倒序遍历

代码如下:

//只能先遍历物品种类,再遍历背包容量(且背包容量只能倒序遍历)
for(int i = 0; i < N; i++) //遍历物品种类
{
	for(int j = V; j >= 0; j--) //遍历背包容量
	{
		if(j >= weight[i])
		{
			dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
		}
		else
		{
			dp[j] = max(dp[j],dp[j - weight[i]] + value[i]);
		}
	}
}

完整代码如下:

#include<iostream>
#include<vector>
using namespace std;

int main()
{
	int N, V;
	cin >> N >> V;
	vector<int>weight(N);
	vector<int>value(N);
	for(int i = 0; i < N; i++)
	{
		cin >> weight[i] >> value[i];
	}
	vector<int>dp(V+1,0);
	//初始化dp数组
	dp[0] = 0;
	//只能先遍历物品种类,再遍历背包容量(且背包容量只能倒序遍历)
	for(int i = 0; i < N; i++) //遍历物品种类
	{
		for(int j = V; j >= 0; j--) //遍历背包容量
		{
			if(j >= weight[i])
			{
				dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
			}
			else
			{
				dp[j] = dp[j];
			}
		}
	}
	cout << dp[V] << endl;
	
	return 0;
}

2、完全背包问题

问题描述:

给定物体数量N,以及背包能够装下的最大重量V,对于物品i, 其重量为 weight[i],价值为value[i]。每种物品可以拿无限多次,求在不超过背包重量的前提下,能够拿到的物品的总价值的最大值是多少?

解决思路:

对于完全背包问题,解决思路跟01背包问题几乎相同,唯一不同的地方在于,再遍历的背包容量的时候采用正序遍历。

代码如下:

#include<iostream>
#include<vector>
using namespace std;

int main()
{
	int N, V;
	cin >> N >> V;
	vector<int>weight(N);
	vector<int>value(N);
	for(int i = 0; i < N; i++)
	{
		cin >> weight[i] >> value[i];
	}
	vector<int>dp(V+1,0);
	//初始化dp数组
	dp[0] = 0;
	//只能先遍历物品种类,再遍历背包容量(且背包容量只能倒序遍历)
	for(int i = 0; i < N; i++) //遍历物品种类
	{
		for(int j = 0; j <= V; j++) //遍历背包容量
		{
			if(j >= weight[i])
			{
				dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
			}
			else
			{
				dp[j] = dp[j];
			}
		}
	}
	cout << dp[V] << endl;
	
	return 0;
}

3、多重背包问题

问题描述:

给定物体数量N,以及背包能够装下的最大重量V,对于物品i, 其重量为 weight[i],价值为value[i]。对于物品i,可以拿size[i]次。求在不超过背包重量的前提下,能够拿到的物品的总价值的最大值是多少?

解决思路:

对于多重背包问题,可以将其转化为01背包问题。

转化思路:
与01背包唯一不同的是,规定了每件物品最多能拿size[i]次,把size[i]件物品摊开,其实就是一个01背包问题了。相当于物品的数量增加了而已。

实现代码如下:

#include<iostream>
#include<vector>
using namespace std;


int main()
{
    int N, V;
    cin >> N >> V;
    vector<int>weight(N); //物品重量
    vector<int>value(N); //物品价值
    vector<int>size(N); //物品件数
    for(int i = 0; i < N; i++)
    {
        cin >> weight[i] >> value[i] >> size[i];
    }
    for(int i = 0; i< N; i++)
    {
        while(size[i] > 1) //把该物品展开,直到剩一件
        {
            weight.push_back(weight[i]);
            value.push_back(value[i]);
            size[i]--;
        }
    }
    int n = weight.size();
    vector<int>dp(V+1);
    for(int i = 0; i < n; i++)
    {
        for(int j = V; j >= weight[i]; j--)
        {
            dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
        }
    }
    cout << dp[V] << endl;
    
    return 0;
}
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值