每日一练:动态规划(01背包问题)01

每日一练:动态规划之背包问题

01背包问题

题目链接:https://www.nowcoder.com/share/jump/3971807151726832163092

题目描述:

你有⼀个背包,最多能容纳的体积是V。
现在有 n 个物品,第 i 个物品的体积为 vi ,价值为 wi。
(1)求这个背包⾄多能装多⼤价值的物品?
(2)若背包恰好装满,求⾄多能装多⼤价值的物品?
输⼊描述:
第⼀⾏两个整数 n 和 V,表⽰物品个数和背包体积。
接下来 n ⾏,每⾏两个数 vi 和 wi,表⽰第i个物品的体积和价值。
输出描述:
输出有两⾏,第⼀⾏输出第⼀问的答案,第⼆⾏输出第⼆问的答案,如果⽆解请输出 0。
⽰例1
输⼊:
3 5
2 10
45
1 4
输出:
14
9
说明:
装第⼀个和第三个物品时总价值最⼤,但是装第⼆个和第三个物品可以使得背包恰好装满且总价
值最⼤。
⽰例2
输⼊:
3 8
12 6
11 8
6 8 输出:
8
0
说明:
装第三个物品时总价值最⼤但是不满,装满背包⽆解。

算法思路:

第一问

  1. 状态表示: dp[i][j] 表⽰:从前i 个物品中挑选,总体积「不超过」j ,所有的选法中,能挑选出来 的最⼤价值。

  2. 状态转移⽅程: 线性 dp 状态转移⽅程分析⽅式,⼀般都是根据「最后⼀步」的状况,来分情况讨论: i. 不选第 i 个物品:相当于就是去前 i - 1 个物品中挑选,并且总体积不超过 j 。此时 dp[i][j] = dp[i - 1][j] ;

ii. 选择第 i 个物品:那么我就只能去前 i - 1 个物品中,挑选总体积不超过 j - v[i] 的物品。此时dp[i][j] =
dp[i - 1][j - v[i]] + w[i] 。但是这种状态不⼀定存在,因此需要特判⼀下。 综上,状态转移⽅程为: dp[i][j]
= max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]) 。

  1. 初始化: 我们多加⼀⾏一列,⽅便我们的初始化,此时仅需将第⼀⾏初始化为 0 即可。因为什么也不选,也能满⾜体积不⼩于 j 的情况,此时的价值为 0 。第一列也初始化为0,因为体积不超过0,即什么都不选,价值自然为0。

  2. 填表顺序: 根据「状态转移⽅程」,我们仅需「从上往下」填表即可。

  3. 返回值: 根据「状态表⽰」,返回 dp[n][V] 。

第二问

第⼆问仅需微调⼀下 dp 过程的五步即可。
因为有可能凑不⻬ j 体积的物品,因此我们把不合法的状态设置为 -1 。

1.状态表⽰: dp[i][j] 表⽰:从前 i 个物品中挑选,总体积「正好」等于 j ,所有的选法中,能挑选出 来的最⼤价值。

  1. 状态转移⽅程: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]) 。 但是在使⽤ dp[i - 1][j - v[i]] 的时候,不仅要判断 j >= v[i] ,⼜要判断 dp[i -1][j
  • v[i]] 表⽰的情况是否存在,也就是 dp[i - 1][j - v[i]] != -1 。
  1. 初始化: 我们多加⼀⾏一列,⽅便我们的初始化: i. 第⼀个格⼦为0 ,因为正好能凑⻬体积为 0 的背包 ii. 但是第⼀⾏后⾯的格⼦都是 -1 ,因为没有物品,⽆法满⾜体积⼤于 0 的情况。 iii.
    第一列初始化都为0,因为要求所选物品的体积等于0,即什么都不选,价值自然为0

4.填表顺序: 根据「状态转移⽅程」,我们仅需「从上往下」填表即可。

5.返回值: 由于最后可能凑不成体积为 V 的情况,因此返回之前需要「特判」⼀下。

代码

优化前的算法代码
cpp

#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 - 1][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 - 1][j - v[i]] != -1)
 dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
 }
 
 cout << (dp[n][V] == -1 ? 0 : dp[n][V]) << endl;
 return 0;
}

java

class Dy {

        public static void main(String[] args) {
            Scanner in = new Scanner(System.in);
            int n = in.nextInt();
            int V = in.nextInt();
            int[] v = new int[n];
            int[] w = new int[n];
            for (int i = 0; i < n; i++) {
                v[i] = in.nextInt();
                w[i] = in.nextInt();
            }
            //多加一行,为了防止下标越界
            int[][] dp = new int[n + 1][V + 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 - 1] >= 0) {
                        dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - v[i - 1]] + w[i - 1]);
                    }
                }
            }
            System.out.println(dp[n][V]);


            int[][] dp1 = new int[n + 1][V + 1];
            for (int j = 1; j <= V; j++) {
                dp1[0][j] = -1;
            }

            for (int i = 1; i <= n; i++) {
                for (int j = 0; j <= V; j++) {
                    dp1[i][j] = dp1[i - 1][j];
                    if (j - v[i - 1] >= 0 && dp1[i - 1][j - v[i - 1]] != -1) {
                        dp1[i][j] = Math.max(dp1[i][j], dp1[i - 1][j - v[i - 1]] + w[i - 1]);
                    }
                }
            }
            if (dp1[n][V] == -1) {
                System.out.println(0);
            } else {
                System.out.println(dp1[n][V]);
            }


        }
    }
}

优化后的算法代码

空间优化: 背包问题基本上都是利⽤「滚动数组」来做空间上的优化: i. 利⽤「滚动数组」优化; ii. 直接在「原始代码」上修改。
在01背包问题中,优化的结果为: i. 删掉所有的横坐标; ii. 修改⼀下j 的遍历顺序。

cpp

#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; j >= v[i]; 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] = -1;
 for(int i = 1; i <= n; i++)
 for(int j = V; j >= v[i]; j--)
 if(dp[j - v[i]] != -1) 
 dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
 
 cout << (dp[V] == -1 ? 0 : dp[V]) << endl;
 return 0;
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值