LeetCode刷题--- 【模板】完全背包

前言:这个专栏主要讲述动态规划算法,所以下面题目主要也是这些算法做的  

我讲述题目会把讲解部分分为3个部分:
1、题目解析

2、算法原理思路讲解

3、代码实现


【模板】完全背包

题目链接:【模板】完全背包_牛客题霸_牛客网

题目

描述

你有一个背包,最多能容纳的体积是V。

现在有n种物品,每种物品有任意多个,第i种物品的体积为 vi​ ,价值为 wi​。

(1)求这个背包至多能装多大价值的物品?

(2)若背包恰好装满,求至多能装多大价值的物品?

输入描述:

第一行两个整数n和V,表示物品个数和背包体积。

接下来n行,每行两个数 vi ​和 wi​,表示第i种物品的体积和价值。

1≤n,V≤1000

输出描述:

输出有两行,第一行输出第一问的答案,第二行输出第二问的答案,如果无解请输出0。

示例1

输入:

2 6
5 10
3 1

复制输出:

10
2

示例2

输入:

3 8
3 10
9 1
10 1

复制输出:

20
0

复制说明:

无法恰好装满背包。

示例3

输入:

6 13
13 189
17 360
19 870
14 184
6 298
16 242

复制输出:

596
189

说明:

可以装5号物品2个,达到最大价值298*2=596,若要求恰好装满,只能装1个1号物品,价值为189.


解法

算法原理与解析

我们这题使用动态规划,我们做这类题目可以分为以下五个步骤

  1. 状态显示
  2. 状态转移方程
  3. 初始化(防止填表时不越界)
  4. 填表顺序
  5. 返回值

第一问

  • 状态显示
dp[ i ][ j ] :表示 从前 i 个物品中挑选,总体积不超过 j ,所有的选法中,能挑选出来的最大价值。
  • 状态转移方程
线性 dp 状态转移方程分析方式,一般都是根据最后⼀步的状况,来分情况讨论。但是最后⼀个物品能选很多个,因此我们的需要分很多情况:
  1. 0 个第 i 个物品:此时相当于就是去前 i - 1 个物品中挑选,总体积不超过 j 。 此时最⼤价值为 dp[i - 1][j]
  2. 1 个第 i 个物品:此时相当于就是去前 i - 1 个物品中挑选,总体积不超过 j - v[i] 。因为挑选了⼀个 i 物品,此时最⼤价值为 dp[i - 1][j - v[i]] + w[i] ;
  3. 2 个第 i 个物品:此时相当于就是去前 i - 1 个物品中挑选,总体积不超过 j - 2 * v[i] 。因为挑选了两个 i 物品,此时最⼤价值为 dp[i - 1][j - 2 * v[i]] + 2 * w[i] ;
  4. 3个第 i 个物品:等等
综上,我们的状态转移方程为:
dp[i][j]=max(dp[i-1][j], dp[i-1][ j-v[i]]+w[i], dp[i-1][j- 2*v[i]]+2*w[i]...)
当我们发现,计算⼀个状态的时候,需要⼀个循环才能搞定的时候,我们要想到去优化。优化的方向就是⽤⼀个或者两个状态来表示这⼀堆的状态,通常就是用数学的方式做⼀下等价替换。我们发现第⼆维是有规律的变化的,因此我们去看看 dp[i][j - v[i]] 这个状态:
dp[i][j-v[i]]=max(dp[i-1][j-v[i]],dp[i-1][j-2*v[i]]+w[i],dp[i-1] [j-3*v[i]]+2*w[i]...)
我们发现,把 dp[i][j - v[i]] 加上 w[i] 正好和 dp[i][j] 中除了第⼀项以外的全部一致,因此我们可以修改我们的状态转移⽅程为:

dp[ i ][ j ] = max(dp[ i - 1 ][ j ], dp[ i ] [ j - v[i]] + w[ i ] ) 

  • 初始化(防止填表时不越界)

我们多加⼀行,方便我们的初始化,此时仅需将第⼀行初始化为 0 即可。因为什么也不选,也能满足体积不小于 j 的情况,此时的价值为 0
  • 填表顺序
根据状态转移方程,我们仅需从上往下填表即可。
  • 返回值
根据状态表示,返回 dp[n][V]

第⼆问
  • 状态显示

dp[ i ][ j ] :表示从前 i 个物品中挑选,总体积不超过 j ,所有的选法中,能挑选出来的最大价值。

  • 状态转移方程
dp[ i ][ j ] = max(dp[ i - 1 ][ j ], dp[ i ][ j - v[ i ] ] + w[ i ] )
但是在使用  dp[ i ][ j - v[ i ]] 的时候,不仅要判断 j >= v[ i ] ,又要判断 dp[ i ][ j - v[i]] 表示的情况是否存在,也就是 dp[i][j - v[i]] != -1
  • 初始化(防止填表时不越界)
我们多加⼀行,方便我们的初始化:
  1. 第⼀个格子为 0 ,因为正好能凑齐体积为 0 的背包;
  2. 但是第⼀行后面的格子都是 -1 ,因为没有物品,无法满足体积大于 0 的情况。
  • 填表顺序
根据状态转移方程,我们仅需从上往下填表即可。
  • 返回值
由于最后可能凑不成体积为 V 的情况,因此返回之前需要特判⼀下。

代码实现

#include <iostream>
#include <string.h>
using namespace std;

const int N = 1010;
int n, V, v[N], w[N];
int dp[N][N];
int main()
{
	// 读入数据
	cin >> n >> V;
	for (int i = 1; i <= n; i++)
		cin >> v[i] >> w[i];

	// 第以问
	for (int i = 1; i <= n; i++)
		for (int j = 0; j <= V; j++)
		{
			dp[i][j] = dp[i - 1][j];
			if (j >= v[i]) 
				dp[i][j] = max(dp[i][j], dp[i][j - v[i]] + w[i]);
		}

	cout << dp[n][V] << endl;

	// 第二问
	memset(dp, 0, sizeof dp);
	for (int j = 1; j <= V; j++)	// 初始化
		dp[0][j] = -1;

	for (int i = 1; i <= n; i++)
		for (int j = 0; j <= V; j++)
		{
			dp[i][j] = dp[i - 1][j];
			if (j >= v[i] && dp[i][j - v[i]] != -1)
				dp[i][j] = max(dp[i][j], dp[i][j - v[i]] + w[i]);
		}
	cout << (dp[n][V] == -1 ? 0 : dp[n][V]) << endl;
	return 0;
}

代码优化

#include <iostream>
#include <string.h>
using namespace std;

const int N = 1010;
int n, V, v[N], w[N];
int dp[N];
int main()
{
	// 读入数据
	cin >> n >> V;
	for (int i = 1; i <= n; i++)
		cin >> v[i] >> w[i];

	// 第一问
	for (int i = 1; i <= n; i++)
		for (int j = v[i]; j <= V; j++)
			dp[j] = max(dp[j], dp[j - v[i]] + w[i]);

	cout << dp[V] << endl;

	// 第二问
	memset(dp, 0, sizeof dp);
	for (int j = 1; j <= V; j++) 
		dp[j] = -0x3f3f3f3f;

	for (int i = 1; i <= n; i++)
		for (int j = v[i]; j <= V; j++)
			dp[j] = max(dp[j], dp[j - v[i]] + w[i]);

	cout << (dp[V] < 0 ? 0 : dp[V]) << endl;
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-元清-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值