题目解析:水果店果篮最小成本问题 | 豆包MarsCode AI 刷题

123 阅读4分钟

豆包MarsCode285 水果店果篮最小成本问题

问题描述

小C开了一家水果店,某天接到了一个大订单,要求将n个编号为1到n的水果打包成多个果篮,每个果篮的最大容量为m个水果,并且果篮中水果的编号必须连续。每个果篮的成本与容纳的水果数量及水果的体积相关,成本公式为:

k×(u+v)/2+sk \times \lfloor(u+v)/2\rfloor + s

其中,uu是果篮中水果的最大体积,vv是果篮中水果的最小体积,kk是果篮中水果的数量,ss是一个常数,x\lfloor x \rfloor 表示对xx进行下取整。

你的任务是帮助小C将这些水果打包成若干果篮,使得总成本最小。

例如:当有6个水果,体积为[1, 4, 5, 1, 4, 1],一个果篮最多装4个水果,常数ss为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

问题链接:水果店果篮最小成本问题 - MarsCode


解决思路

  1. 问题分析

    • 有一个长度为 n 的数组,表示水果的体积。需要将这些水果打包成多个果篮,每个果篮最多包含 m 个水果,且水果的编号要是连续的。

    • 每个果篮的成本计算公式为:k * floor((u + v) / 2) + s,其中:

      • k 是果篮中水果的数量。
      • u 是果篮中水果的最大体积。
      • v 是果篮中水果的最小体积。
      • s 是一个常数。
  2. 解法概要

    • 动态规划:定义 dp[i] 为前 i 个水果打包成若干果篮的最小成本。
    • 状态转移:对于每个水果 i,我们可以选择一个之前的水果 jj < 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
  3. 代码实现

    • 使用稀疏表 stMaxstMin 来存储每个区间的最大值和最小值。
    • 使用 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
    }
}

代码解析:

  1. buildSparseTable

    • 预处理 ST 表,计算出每个区间的最大值和最小值。stMax 存储最大值,stMin 存储最小值。
  2. cost

    • 计算从 lr 的区间的成本,利用 ST 表来快速查询该区间内的最大值 u 和最小值 v,然后根据成本公式计算并返回。

复杂度分析:

  • 动态规划的时间复杂度:对于每个 i,我们最多需要检查 m 个分割点,时间复杂度为 O(n * m)