day24 426 开心的金明 (01背包问题)

这篇博客介绍了一种将物品选择优化问题转化为01背包问题的策略。通过将总钱数视为背包容量,物品价格乘以重要度视为价值,利用动态规划算法求解在不超过总钱数的情况下,使得物品价格与重要度乘积总和最大的购物单。博客详细阐述了从二维到一维动态规划的优化过程,并提供了Java代码实现。
摘要由CSDN通过智能技术生成

426. 开心的金明

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。

更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 N N N元钱就行”。

今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的 N N N元。

于是,他把每件物品规定了一个重要度,分为 5 5 5等:用整数 1 1 1~ 5 5 5表示,第 5 5 5等最重要。

他还从因特网上查到了每件物品的价格(都是整数元)。

他希望在不超过 N N N元(可以等于 N N N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。

设第 j j j件物品的价格为 v [ j ] v[j] v[j],重要度为 w [ j ] w[j] w[j],共选中了 k k k件物品,编号依次为 j 1 , j 2 , … , j k j_1,j_2,…,j_k j1j2jk,则所求的总和为:

v [ j 1 ] ∗ w [ j 1 ] + v [ j 2 ] ∗ w [ j 2 ] + … + v [ j k ] ∗ w [ j k ] v[j_1]∗w[j_1]+v[j_2]∗w[j_2]+…+v[j_k]∗w[j_k] v[j1]w[j1]+v[j2]w[j2]++v[jk]w[jk]
请你帮助金明设计一个满足要求的购物单。

输入格式
输入文件的第 1 1 1行,为两个正整数 N N N m m m,用一个空格隔开。(其中 N N N表示总钱数, m m m为希望购买物品的个数)

从第 2 2 2行到第 m + 1 m+1 m+1行,第 j j j行给出了编号为 j − 1 j-1 j1的物品的基本数据,每行有 2 2 2个非负整数 v v v p p p。(其中 v v v表示该物品的价格, p p p表示该物品的重要度)

输出格式
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(数据保证结果不超过 100000000 100000000 100000000)。

数据范围
1 ≤ N < 30000 , 1≤N<30000, 1N<30000,
1 ≤ m < 25 , 1≤m<25, 1m<25,
0 ≤ v ≤ 10000 , 0≤v≤10000, 0v10000,
1 ≤ p ≤ 5 1≤p≤5 1p5
输入样例:

1000 5
800 2
400 5
300 5
400 3
200 2

输出样例:

3900

思路:

将原问题做如下转化

  • 总钱数相当于背包总容量;
  • 每件物品的价格相当于体积;
  • 每件物品的价格乘以重要度相当于价值;

那么就变成了经典的01背包问题。
在这里插入图片描述
状态计算: f[i][j] 表示所有从前i个物品选,且总体积不超过j的选法集合中的价值最大值

那么f[m][n]就表示从前m种物品中选,且总体积不超过n的所有选法集合的价值最大值,即为答案。

集合划分:

按照第i种物品选或者不选划分 f[i][j]集合。

  • 不选第i种物品,f[i][j] = f[i - 1][j];
    问题转化为从前i - 1个物品选,且总体积不超过j的选法集合中的最大值。

  • 选第i种物品, f[i][j] = f[i - 1][j - v] + v * w;
    已经确定选第i种物品,那么问题转化为从前i - 1个物品选,且总体积不超过j - v的选法集合中的最大值再加上 当前物品的价值v * w

状态计算方程:
f[i][j] = max(f[i - 1][j],f[i - 1][j - v] + v * w)

Java代码

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();//总共n元钱
        int m = scanner.nextInt();//m件物品
        int[] v = new int[m + 1];//每件物品的价格
        int[] w = new int[m + 1];//每件物品的重要度
        int[][] f = new int[m + 1][n + 1];
        for(int i = 1;i <= m;i++){
            v[i] = scanner.nextInt();
            w[i] = scanner.nextInt();
        }
        //初始化f数组,因为后面会出现i-1,故i需要大于等于1,故初始化i为0的情况,然后让i从1开始
        //事实上他们默认就是0,不过写上引起注意,记住有这个细节
        for(int j = 0;j < n;j++){
            f[0][j] = 0;
        }
        //现在i可以从1开始了
        for(int i = 1;i <= m;i++){
            for(int j = 0;j <= n;j++){
                f[i][j] = f[i-1][j];
                if(j >= v[i]){
                    f[i][j] = Math.max(f[i - 1][j],f[i-1][j - v[i]] + v[i] * w[i]);
                }
            }
        }
        System.out.println(f[m][n]);
    }
}

考虑优化为一维DP
可先看完day15 1371 货币系统(完全背包问题)的优化为一维DP部分,然后再看这里的优化部分,相互补充,理解更为深刻。

假设你已经看完了day15 1371 货币系统(完全背包问题),好的,现在开始优化本题。

我们不难发现状态计算时f[i]层的更新只用到了f[i-1]层,因此我们可以去掉前一维

状态计算方程变为 :f[j] = max(f[j], f[j-v]+v*w);

代码表示:

for(int j = v[i]; j <= n; j++) 
{
   f[j] = Math.max(f[j], f[j-v[i]]+v[i]*w[i]);
}

但是此时不和之前的代码f[i][j] = Math.max(f[i - 1][j],f[i-1][j - v[i]] + v[i] * w[i])等价了,因为在计算第i层的状态时,我们从小到大枚举体积,体积j-v[i]严格小于j,那么f[j-v[i]]会先于f[j]被计算出来,此时我们这里的f[j-v[i]]已经为第i层状态,这样f[j-v[i]]等价于f[i][j-v[i]] ,实际上我们需要的是f[j-v[i]]得等价于f[i-1][j-v[i]]才行。

为了解决这个问题只需要体积从大到小枚举,

for(int j = n ; j >= v[i]; j--) 
{
   f[j] = Math.max(f[j], f[j-v[i]]+v[i]*w[i]); 
}

因为我们从大到小枚举体积,而j - v[j]严格小于j我们在计算f[j]的时候f[j-v[i]]还未被第i层状态更新过,那么它存的就是上一层(i-1层)的状态,即f[i-1][j-v[i]]

参考了AcWing z林深时见鹿 的题解

Java代码

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();//总共n元钱
        int m = scanner.nextInt();//m件物品
        int[] v = new int[m + 1];//每件物品的价格
        int[] w = new int[m + 1];//每件物品的重要度
        int[] f = new int[n + 1];
        for(int i = 1;i <= m;i++){
            v[i] = scanner.nextInt();
            w[i] = scanner.nextInt();
        }
        
        
        for(int i = 1;i <= m;i++){
            for(int j = n;j >=v[i]; j--){
                    f[j] = Math.max(f[j],f[j - v[i]] + v[i] * w[i]);
            }
        }
        System.out.println(f[n]);
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值