No83 小S的货船租赁冒险
问题描述:
小S在码头租用货船,有 Q 种不同类型的货船可供选择。每种货船有固定的数量 m[i]、租赁成本 v[i] 和最大载货量 w[i]。小S希望在预算 V 元内,租用能够承载最大总货物的货船组合。每种货船的具体信息包括数量、租赁价格和载货量。小S需要你帮忙计算在给定预算下,她能租用的货船的最大总载货量是多少。
Q: 货船的种类数量。V: 李华可用的总预算(单位:元)。ships: 一个列表,其中每个元素是一个元组[m[i], v[i], w[i]],分别表示第i种货船的数量、租赁价格和每艘货船的最大载货量。
问题分析:
1、分析问题类型
在给定预算的条件下,从多种类型的货船中选择合适的组合,使得载货总量最大,这属于背包问题(在有限容量的情况下选择物品以达到某种最优价值)的变形。可以使用动态规划来解决这类问题。
2. 确定状态表示。
定义一个二维数组 dp[i][j],表示在前 i 种货船中,使用不超过 j 元的预算所能获得的最大载货量。
3. 初始化状态
- 创建一个二维数组
dp,大小为(Q+1) x (V+1),初始值为 0。 - 当
i = 0(也就是还没有选择任何货船种类时),对于所有的预算j,如果j小于第一种货船的租赁成本v[0],那么dp[0][j] = 0,因为此时没办法租用第一种货船,载货量自然为 0;而如果j大于等于v[0],则dp[0][j]等于在预算j下能租用第一种货船所获得的载货量,也就是(j / v[0]) * w[0](这里j / v[0]表示能租用的第一种货船的数量,向下取整,再乘以每艘的载货量w[0])。 - 另外,当
j = 0(预算为 0 时),无论考虑到第几种货船,dp[i][0] = 0,因为没有预算就租不了任何货船,载货量就是 0。
4. 状态转移方程推导
-
对于每一种货船
i,遍历所有可能的预算j。 -
对于每种货船,考虑租用
k艘(k从 0 到m[i]),更新dp[i][j]。 -
对于
i > 0和j > 0的情况,我们需要考虑是否选择第i种货船来更新最大载货量。 -
有两种选择:
- 不选第
i种货船:那么dp[i][j] = dp[i - 1][j],也就是维持在前i - 1种货船、预算为j时的最大载货量状态。 - 选择第
i种货船:我们要考虑在预算允许的范围内尽可能多地租用第i种货船,设能租用的最大数量为k(k要满足k <= m[i]且k * v[i] <= j),然后计算选择k艘第i种货船后的载货量,即k * w[i],再加上用剩余预算j - k * v[i]在前i - 1种货船中能获得的最大载货量dp[i - 1][j - k * v[i]]。我们要在所有可能的k值中取使得载货量最大的情况。
- 不选第
-
所以状态转移方程为:
dp[i][j] = max(dp[i][j], dp[i-1][j-k*v[i]] + k*w[i]),其中k是租用的货船数量。
5. 计算最终结果
- 通过按照状态转移方程依次填充
dp数组,最后dp[Q][V]就表示在Q种货船、预算为V元的情况下,所能租用的货船的最大总载货量。
6. 代码
public static int solution(int Q, int V, List<List<Integer>> ships) {
// Please write your code here
// Q:货船种类
// V: 总预算
// 初始化动态规划数组
int[][] dp = new int[Q + 1][V + 1];
// 遍历每一种货船
for (int i = 1; i <= Q; i++) {
List<Integer> ship = ships.get(i - 1);
int m = ship.get(0); // 货船数量
int v = ship.get(1); // 租赁成本
int w = ship.get(2); // 载货量
// 遍历所有可能的预算
for (int j = 0; j <= V; j++) {
// 考虑租用 k 艘货船
for (int k = 0; k <= m && k * v <= j; k++) {
// 更新 dp[i][j]
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * v] + k * w);
}
}
}
// 返回最终结果
return dp[Q][V];
}