AI 刷题2 徒步旅行中的补给问题 题解 | 豆包MarsCode AI刷题

28 阅读3分钟

问题背景

想象一下,我们的朋友小R正在进行一场徒步旅行。这次旅程需要 N 天完成。每天,小R都会经过一个补给站,可以购买食物补充能量。不过,补给站的食物价格可不便宜,而且小R的背包只能装 K 份食物,这意味着他需要非常精打细算,才能以最低的花费完成整个旅程。

问题的核心是:

  • 小R每天消耗 1 份食物,但由于背包容量有限,他必须决定何时购买多少食物,才能既满足需求,又最小化支出。

问题描述

  1. 输入

    • N: 徒步旅行的总天数。
    • K: 背包最多能装的食物份数。
    • data[i]: 第 i 天的食物单价。
  2. 输出

    • 最低的总花费,让小R能够顺利完成旅程。

动态规划解法的直观解释

我们用 动态规划 来解决这个问题,模拟小R每天背包里的状态,计算出到达每一天时的最小花费。

核心思想

假设我们用 dp[i][j] 表示:

  • 到第 i 天时,背包里剩下 j 份食物的最小花费。

为了计算 dp[i][j],我们需要考虑:

  1. i-1 天的背包里有多少食物剩余(假设为 l)。
  2. i 天需要购买多少食物,才能保证背包在消耗 1 份之后,仍然有 j 份食物。

食物购买量为:j - l + 1,需要满足以下条件:

  • 购买量不能超过背包容量 K
  • 购买量不能为负,即不能“卖出”食物。

状态转移方程

状态转移关系可以表示为:

dp[i][j]=min⁡(dp[i][j],dp[i−1][l]+(j−l+1)×data[i−1])dp[i][j] = \min(dp[i][j], dp[i-1][l] + (j - l + 1) \times data[i-1])dp[i][j]=min(dp[i][j],dp[i−1][l]+(j−l+1)×data[i−1])

其中:

  • l 是第 i-1 天剩余的食物量。
  • (j - l + 1) 是第 i 天需要购买的食物数量。

动态规划实现(C++)

以下是完整的 C++ 实现代码:

cpp
复制代码
#include <iostream>
#include <vector>
#include <climits> // for INT_MAX
using namespace std;

int solution(int n, int k, vector<int> data) {
    // 初始化 dp 数组
    vector<vector<int>> dp(n + 1, vector<int>(k + 1, INT_MAX));
    dp[0][0] = 0; // 第 0 天无花费

    // 动态规划计算最优解
    for (int i = 1; i <= n; i++) {           // 遍历每一天
        for (int j = 0; j <= k; j++) {       // 当前背包剩余食物量
            for (int l = 0; l <= k; l++) {   // 前一天背包剩余食物量
                int buy = j - l + 1;         // 需要购买的食物数量
                if (buy >= 0 && buy <= k) {  // 确保合法
                    if (dp[i - 1][l] != INT_MAX) { // 前一天有解
                        dp[i][j] = min(dp[i][j], dp[i - 1][l] + buy * data[i - 1]);
                    }
                }
            }
        }
    }

    // 返回结果:第 n 天背包空的最小花费
    return dp[n][0];
}

int main() {
    // 测试样例
    cout << (solution(5, 2, {1, 2, 3, 3, 2}) == 9) << endl;
    cout << (solution(6, 3, {4, 1, 5, 2, 1, 3}) == 9) << endl;
    cout << (solution(4, 1, {3, 2, 4, 1}) == 10) << endl;
    return 0;
}

解题过程与优化

如何理解状态转移?

我们以 solution(5, 2, {1, 2, 3, 3, 2}) 为例:

  1. 第 1 天

    • 小R需要买 1 份食物,花费 1
    • 背包中剩余 1 份食物,状态为 dp[1][1] = 1
  2. 第 2 天

    • 小R从背包中取出 1 份食物,背包空了。
    • 他又需要买 2 份,背包装满,花费 2 * 2 = 4,状态为 dp[2][2] = 5
  3. 依此类推,动态规划会逐步计算每一天的最优策略。

时间复杂度分析

  1. 三重循环:

    • 天数循环:O(n)
    • 背包剩余状态循环:O(k)
    • 状态转移循环:O(k)
  2. 总时间复杂度:O(n * k^2)


思考与优化

1. 空间优化

我们可以使用滚动数组,将二维数组优化为一维数组,减少空间消耗。

2. 加速状态转移

利用单调队列维护最小值,可以将状态转移的复杂度从 O(k) 降低到 O(1),整体时间复杂度降为 O(n * k)