题目描述
老李是货运公司承运人,老李的货车额定载货重量为 wt。
现有两种货物:
- 货物 A 单件重量为 wa,单件运费利润为 pa
- 货物 B 单件重量为 wb,单件运费利润为 pb
老李每次发车时载货总重量刚好为货车额定的载货重量 wt,车上必须同时有货物 A 和货物 B ,货物A、B不可切割。
老李单次满载运输可获得的最高利润是多少?
输入描述
第一列输入为货物 A 的单件重量 wa
- 0 < wa < 10000
第二列输入为货物 B 的单件重量 wb
- 0 < wb < 10000
第三列输入为货车的额定载重 wt
- 0 < wt < 100000
第四列输入为货物 A 的单件运费利润 pa
- 0 < pa < 1000
第五列输入为货物 B 的单件运费利润 pb
- 0 < pb < 1000
输出描述
单次满载运输的最高利润
用例
输入 | 10 8 36 15 7 |
输出 | 44 |
说明 | 无 |
输入 | 1 1 2 1 1 |
输出 | 2 |
说明 | 无 |
题目解析
本题其实第一眼看过去就是完全背包问题,且是需要装满背包的完全背包问题。
但是具体读题后,发现这题的货物只有两种,因此使用完全背包解题反而有点复杂了。
如果货物有多种的话,那么此题用完全背包解题是非常合适的。
因此,我这里提供了两种解法,一种是暴力枚举,一种是完全背包。其中完全背包解法,主要是让大家熟悉下背包问题中,需要恰好装满背包时的dp初始化逻辑。
但是本题考试时可以选择更简单的暴力枚举方式。
暴力枚举解法
假设 x 件A货物,和 y 件B货物,可以装满货车,那么有关系式如下:
wa * x + wb * y = wt
我们可以选择其中的 x 或者 y 进行枚举可能数量,比如 A 货物的件数 x 至少有1件,至多有(wt – wb) / wa 件。
我们枚举A货物数量范围的所有值作为x去尝试,那么只要 (wt – wa * x) % wb == 0(即y是整数),那么就找到了一组可能解x,y。计算此时的货物利润 pa * x + pb * y 。
最后保留最大的利润即可。
JS算法源码
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
// 输入输出处理
void (async function () {
let [wa, wb, wt, pa, pb] = (await readline()).split(" ").map(Number);
// 装入货车的A货物数量至少1件,至多(wt - wb) / wa件
const minX = 1;
const maxX = Math.floor((wt - wb) / wa);
// 记录最大利润
let ans = 0;
// 枚举A货物的可能数量
for (let x = minX; x <= maxX; x++) {
// B货物可能的总重量
const remain = wt - wa * x;
if (remain % wb == 0) {
// B货物的数量
const y = remain / wb;
// 计算利润,保留最大利润
ans = Math.max(ans, pa * x + pb * y);
}
}
console.log(ans);
})();
Java算法源码
import java.util.Scanner;
public class Main {
// 输入输出处理
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int wa = sc.nextInt();
int wb = sc.nextInt();
int wt = sc.nextInt();
int pa = sc.nextInt();
int pb = sc.nextInt();
// 装入货车的A货物数量至少1件,至多(wt - wb) / wa件
int minX = 1;
int maxX = (wt - wb) / wa;
// 记录最大利润
int ans = 0;
// 枚举A货物的可能数量
for (int x = minX; x <= maxX; x++) {
// B货物可能的总重量
int remain = wt - wa * x;
if (remain % wb == 0) {
// B货物的数量
int y = remain / wb;
// 计算利润,保留最大利润
ans = Math.max(ans, pa * x + pb * y);
}
}
System.out.println(ans);
}
}
Python算法源码
# 输入获取
wa, wb, wt, pa, pb = map(int, input().split())
# 装入货车的A货物数量至少1件,至多(wt - wb) / wa件
minX = 1
maxX = (wt - wb) // wa
# 记录最大利润
ans = 0
# 枚举A货物的可能数量
for x in range(minX, maxX + 1):
# B货物可能的总重量
remain = wt - wa * x
if remain % wb == 0:
# B货物的数量
y = remain // wb
# 计算利润,保留最大利润
ans = max(ans, pa * x + pb * y)
print(ans)
C算法源码
#include <stdio.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
int wa, wb, wt, pa, pb;
scanf("%d %d %d %d %d", &wa, &wb, &wt, &pa, &pb);
// 装入货车的A货物数量至少1件,至多(wt - wb) / wa件
int minX = 1;
int maxX = (wt - wb) / wa;
// 记录最大利润
int ans = 0;
// 枚举A货物的可能数量
for (int x = minX; x <= maxX; x++) {
// B货物可能的总重量
int remain = wt - wa * x;
if (remain % wb == 0) {
// B货物的数量
int y = remain / wb;
// 计算利润,保留最大利润
ans = MAX(ans, pa * x + pb * y);
}
}
printf("%dn", ans);
return 0;
}
完全背包解法
本题可以转化为完全背包问题。
完全背包问题是指:
- 有 N 件物品,每件物品的重量为w[i],价值为p[i],数量不限。
- 有一个背包,背包承重为 W
问:不超出背包承重的情况下,装入背包物品的最大价值是多少?
本题中:
- 货物A,B => 物品
- 货车 => 背包
- 货物A,B的单件重量 => 物品的重量
- 货物A,B的单件利润 => 物品的价值
- 货车的载重 => 背包的载重
另外,本题作了如下改动:
- 本题要求货车上必须装入A,B货物
- 本题要求货车必须装满
对于改动1,我们可以预先给货车装一件A,一件B,此时:
- 货车承重 = wt -(wa + wb)
- 已获得利润 = pa + pb
即可转化为完全背包问题为:
- 背包承重:wt -(wa + wb)
- 物品重量:wa、wb
- 物品价值:pa、pb
- 物品数量:无限
对于改动2,要求装满背包,而装满背包和不装满背包的区别仅在于dp初始化:
- 不强制装满背包的问题,dp数组元素可以全部初始化为0,
- 强制装满背包的问题,dp数组元素只有dp[0][0]初始化为0,其余dp元素需要初始化为负无穷
对于要求装满背包的场景,关于dp元素(除dp[0][0)初始化为负无穷的原因,在解释之前,我们应该对dp数组有个初步的了解:
dp[i][j] 表示:从前 i 种物品选择,装满承重为 j 的背包,所能获得的最大价值,而 dp[i][j] 可以由之前的dp状态递推得到,递推公式(状态转移方程)如下:
dp[i][j] = max(dp[i-1][j], dp[i-1][j – w[i]] + p[i])
上面递推公式的含义为:
对于第 i 个物品,我们可以选择装或者不装:
- 如果不装的话,则 dp[i][j] = dp[i-1][j]
- 如果装的话,则 dp[i][j] = dp[i-1][j – w[i]] + p[i]
更多细节请看:
算法设计 – 01背包问题的状态转移方程优化,以及完全背包问题_背包问题状态转移方程-CSDN博客
在前面状态转移方程中,如果我们选择装入第 i 个物品,那么:
如果 dp[i-1][j – w[i]] 等于负无穷的话,则 dp[i-1][j – w[i]] + p[i] 结果也是负无穷,即dp[i][j]为负无穷。
而dp元素值负无穷的含义是,对应物品选择范围下,不存在可以装满背包的方案。
因此如果前 i – 1 个物品选择组合,不存在可以装满 j – w[i] 承重的背包的方案,那么即使我们后面选择加入了第 i 个物品,也无法装满 j 承重的背包。
相反的,如果 dp[i-1][j – w[i]] ≥ 0 的话,则说明前 i – 1 个物品选择组合,存在可以装满 j – w[i] 承重的背包的方案,且获得的最大价值为dp[i-1][j – w[i]]。
那么我们后面选择加入第 i 个物品的话,也一定可以装满 j 承重的背包。此时 dp[i-1][j – w[i]] + p[i] 也必然 ≥ 0。
因此,dp元素值初始化为负无穷的作用就是,对于无法装满背包的情况进行标记,如果用于递推dp[i][j]的dp值无法装满背包,则dp[i][j]也必然无法装满背包。
JS算法源码
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
// 输入输出处理
void (async function () {
let [wa, wb, wt, pa, pb] = (await readline()).split(" ").map(Number);
// 背包承重, 这里减去必然需要装入的一件A货物和一件B货物
wt -= wa + wb;
// 物品的重量
const w = [wa, wb];
// 物品的价值
const p = [pa, pb];
// maxP是装满承重为 wt 的背包的最大价值
const maxP = getResult(wt, 2, w, p);
if (maxP >= 0) {
// 如果maxP是非负数,则存在装满背包的方案,注意最后返回结果需要加上初始装上车的一件A和一件B的利润
console.log(maxP + pa + pb);
} else {
// 如果maxP是负数,则说明不存在装满wt的方案,此时直接直接0
console.log(0);
}
})();
/**
* 完全背包
*
* @param bag 背包承重
* @param n 物品种数
* @param w 物品的重量数组
* @param p 物品的价值数组
* @return 装满背包的最大价值
*/
function getResult(bag, n, w, p) {
// dp[i]表示装满承重为 i 的背包的最大价值
// 装满背包的背包问题,初始化时需要将除了dp[0]外的dp元素值设为负无穷
const dp = new Array(bag + 1).fill(-Infinity);
dp[0] = 0;
// 遍历物品
for (let i = 0; i < n; i++) {
// 遍历背包承重,完全背包这里要正序遍历
for (let j = w[i]; j <= bag; j++) {
dp[j] = Math.max(dp[j], dp[j - w[i]] + p[i]);
}
}
// 返回装满承重为bag的背包的最大价值
return dp[bag];
}
Java算法源码
import java.util.Arrays;
import java.util.Scanner;
public class Main {
// 输入输出处理
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int wa = sc.nextInt();
int wb = sc.nextInt();
int wt = sc.nextInt();
int pa = sc.nextInt();
int pb = sc.nextInt();
// 背包承重, 这里减去必然需要装入的一件A货物和一件B货物
wt -= wa + wb;
// 物品的重量
int[] w = new int[] {wa, wb};
// 物品的价值
int[] p = new int[] {pa, pb};
// maxP是装满承重为 wt 的背包的最大价值
int maxP = getResult(wt, 2, w, p);
if (maxP >= 0) {
// 如果maxP是非负数,则存在装满背包的方案,注意最后返回结果需要加上初始装上车的一件A和一件B的利润
System.out.println(maxP + pa + pb);
} else {
// 如果maxP是负数,则说明不存在装满wt的方案,此时直接直接0
System.out.println(0);
}
}
/**
* 完全背包
*
* @param bag 背包承重
* @param n 物品种数
* @param w 物品的重量数组
* @param p 物品的价值数组
* @return 装满背包的最大价值
*/
public static int getResult(int bag, int n, int[] w, int[] p) {
// dp[i]表示装满承重为 i 的背包的最大价值
int[] dp = new int[bag + 1];
// 装满背包的背包问题,初始化时需要将除了dp[0]外的dp元素值设为负无穷
Arrays.fill(dp, Integer.MIN_VALUE);
dp[0] = 0;
for (int i = 0; i < n; i++) { // 遍历物品
for (int j = w[i]; j <= bag; j++) { // 遍历背包承重,完全背包这里要正序遍历
dp[j] = Math.max(dp[j], dp[j - w[i]] + p[i]);
}
}
// 返回装满承重为bag的背包的最大价值
return dp[bag];
}
}
Python算法源码
import sys
# 输入获取
wa, wb, wt, pa, pb = map(int, input().split())
# 算法入口
def getResult(bag, n, w, p):
"""
完全背包
:param bag: 背包承重
:param n: 物品种数
:param w: 物品的重量数组
:param p: 物品的价值数组
:return: 装满背包的最大价值
"""
# dp[i]表示装满承重为 i 的背包的最大价值
# 装满背包的背包问题,初始化时需要将除了dp[0]外的dp元素值设为负无穷
dp = [-sys.maxsize for _ in range(bag + 1)]
dp[0] = 0
# 遍历物品
for i in range(n):
# 遍历背包承重,完全背包这里要正序遍历
for j in range(w[i], bag + 1):
dp[j] = max(dp[j], dp[j - w[i]] + p[i])
# 返回装满承重为bag的背包的最大价值
return dp[bag]
# 算法调用
# 背包承重, 这里减去必然需要装入的一件A货物和一件B货物
wt -= wa + wb
# 物品的重量
w = [wa, wb]
# 物品的价值
p = [pa, pb]
# maxP是装满承重为 wt 的背包的最大价值
maxP = getResult(wt, 2, w, p)
if maxP >= 0:
# 如果maxP是非负数,则存在装满背包的方案,注意最后返回结果需要加上初始装上车的一件A和一件B的利润
print(maxP + pa + pb)
else:
# 如果maxP是负数,则说明不存在装满wt的方案,此时直接直接0
print(0)
C算法源码
#include <stdio.h>
#include <limits.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
/**
* 完全背包
*
* @param bag 背包承重
* @param n 物品种数
* @param w 物品的重量数组
* @param p 物品的价值数组
* @return 装满背包的最大价值
*/
int getResult(int bag, int n, const int w[], const int p[]) {
// dp[i]表示装满承重为 i 的背包的最大价值
int dp[bag + 1];
// 装满背包的背包问题,初始化时需要将除了dp[0]外的dp元素值设为负无穷
dp[0] = 0;
for (int i = 1; i <= bag; i++) {
dp[i] = INT_MIN;
}
// 遍历物品
for (int i = 0; i < n; i++) {
// 遍历背包承重,完全背包这里要正序遍历
for (int j = w[i]; j <= bag; j++) {
dp[j] = MAX(dp[j], dp[j - w[i]] + p[i]);
}
}
// 返回装满承重为bag的背包的最大价值
return dp[bag];
}
int main() {
int wa, wb, wt, pa, pb;
scanf("%d %d %d %d %d", &wa, &wb, &wt, &pa, &pb);
// 背包承重, 这里减去必然需要装入的一件A货物和一件B货物
wt -= wa + wb;
// 物品的重量
int w[] = {wa, wb};
// 物品的价值
int p[] = {pa, pb};
// maxP是装满承重为 wt 的背包的最大价值
int maxP = getResult(wt, 2, w, p);
if (maxP >= 0) {
// 如果maxP是非负数,则存在装满背包的方案,注意最后返回结果需要加上初始装上车的一件A和一件B的利润
printf("%dn", maxP + pa + pb);
} else {
// 如果maxP是负数,则说明不存在装满wt的方案,此时直接直接0
puts("0");
}
return 0;
}
免责声明:
评论0