豆包MarsCode285 水果店果篮最小成本问题
问题描述
小C开了一家水果店,某天接到了一个大订单,要求将n个编号为1到n的水果打包成多个果篮,每个果篮的最大容量为m个水果,并且果篮中水果的编号必须连续。每个果篮的成本与容纳的水果数量及水果的体积相关,成本公式为:
其中,是果篮中水果的最大体积,是果篮中水果的最小体积,是果篮中水果的数量,是一个常数, 表示对进行下取整。
你的任务是帮助小C将这些水果打包成若干果篮,使得总成本最小。
例如:当有6个水果,体积为[1, 4, 5, 1, 4, 1],一个果篮最多装4个水果,常数为3时,最优的打包方案是将前三个水果(1, 4, 5)装成一个果篮,后三个水果(1, 4, 1)装成另一个果篮,最小的总成本为21。
测试样例
样例1:
输入:
n = 6, m = 4, s = 3, a = [1, 4, 5, 1, 4, 1]输出:21
样例2:
输入:
n = 5, m = 3, s = 2, a = [2, 3, 1, 4, 6]输出:17
样例3:
输入:
n = 7, m = 4, s = 5, a = [3, 6, 2, 7, 1, 4, 5]输出:35
解决思路
-
问题分析:
-
有一个长度为
n的数组,表示水果的体积。需要将这些水果打包成多个果篮,每个果篮最多包含m个水果,且水果的编号要是连续的。 -
每个果篮的成本计算公式为:
k * floor((u + v) / 2) + s,其中:k是果篮中水果的数量。u是果篮中水果的最大体积。v是果篮中水果的最小体积。s是一个常数。
-
-
解法概要:
- 动态规划:定义
dp[i]为前i个水果打包成若干果篮的最小成本。 - 状态转移:对于每个水果
i,我们可以选择一个之前的水果j(j < i),表示a[j..i-1]是一个果篮,然后dp[i] = min(dp[i], dp[j] + cost(j, i-1))。 - ST 表优化:对于每个
cost(j, i-1),需要快速计算区间[j, i-1]的最大值和最小值,这是静态区间最值,这时可以使用ST表来优化区间最大值和最小值的计算。ST表讲解可以看ST 表 - OI Wiki
- 动态规划:定义
-
代码实现:
- 使用稀疏表
stMax和stMin来存储每个区间的最大值和最小值。 - 使用 ST 表加速区间查询,减少计算时间。
- 使用稀疏表
完整代码:
import java.util.Arrays;
public class Main {
// 构建ST表预处理区间最大值和最小值
public static void buildSparseTable(int[] a, int n, int[][] stMax, int[][] stMin) {
for (int i = 0; i < n; i++) {
stMax[i][0] = a[i];
stMin[i][0] = a[i];
}
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 0; i + (1 << j) - 1 < n; i++) {
stMax[i][j] = Math.max(stMax[i][j - 1], stMax[i + (1 << (j - 1))][j - 1]);
stMin[i][j] = Math.min(stMin[i][j - 1], stMin[i + (1 << (j - 1))][j - 1]);
}
}
}
// 计算某一区间的成本
public static int cost(int l, int r, int[] a, int s, int[][] stMax, int[][] stMin) {
// 使用ST表快速查询最大值和最小值
int k = (int) (Math.log(r - l + 1) / Math.log(2)); // 计算区间长度的对数
int mx = Math.max(stMax[l][k], stMax[r - (1 << k) + 1][k]);
int mn = Math.min(stMin[l][k], stMin[r - (1 << k) + 1][k]);
return (mx + mn) / 2 * (r - l + 1) + s;
}
// 计算最小总成本
public static int solution(int n, int m, int s, int[] a) {
// ST表声明
int[][] stMax = new int[n][32];
int[][] stMin = new int[n][32];
buildSparseTable(a, n, stMax, stMin); // 构建ST表
int[] dp = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE); // 初始化dp数组
dp[0] = 0; // 没有水果时,成本为0
for (int i = 1; i <= n; i++) {
// 遍历所有可能的分割点j(最大为m个水果)
for (int j = Math.max(0, i - m); j < i; j++) {
dp[i] = Math.min(dp[i], dp[j] + cost(j, i - 1, a, s, stMax, stMin));
}
}
return dp[n]; // 返回最小总成本
}
public static void main(String[] args) {
System.out.println(solution(6, 4, 3, new int[]{1, 4, 5, 1, 4, 1}) == 21); // 测试样例1
System.out.println(solution(5, 3, 2, new int[]{2, 3, 1, 4, 6}) == 17); // 测试样例2
System.out.println(solution(7, 4, 5, new int[]{3, 6, 2, 7, 1, 4, 5}) == 35); // 测试样例3
}
}
代码解析:
-
buildSparseTable:- 预处理 ST 表,计算出每个区间的最大值和最小值。
stMax存储最大值,stMin存储最小值。
- 预处理 ST 表,计算出每个区间的最大值和最小值。
-
cost:- 计算从
l到r的区间的成本,利用 ST 表来快速查询该区间内的最大值u和最小值v,然后根据成本公式计算并返回。
- 计算从
复杂度分析:
- 动态规划的时间复杂度:对于每个
i,我们最多需要检查m个分割点,时间复杂度为O(n * m)。