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

49 阅读5分钟

问题描述

小R正在计划一次从地点A到地点B的徒步旅行,总路程需要 N 天。为了在旅途中保持充足的能量,小R每天必须消耗1份食物。幸运的是,小R在路途中每天都会经过一个补给站,可以购买食物进行补充。然而,每个补给站的食物每份的价格可能不同,并且小R最多只能同时携带 K 份食物。

现在,小R希望在保证每天都有食物的前提下,以最小的花费完成这次徒步旅行。你能帮助小R计算出最低的花费是多少吗?

问题分析

这道题的本质是一个动态规划问题,目的是在每天都必须有食物的前提下,通过购买和携带的策略,求解以最小花费完成旅行的方式。问题中涉及以下关键限制:

  1. 每天必须消耗1份食物。
  2. 每个补给站的食物价格不同。
  3. 小R最多只能携带 K 份食物。

需要找到最小花费的购买方案,这要求综合考虑:

  • 何时购买食物?
  • 购买多少食物?

# 代码详解

def solution(n, k, data):
    # Edit your code here
    dp = [[float('inf')] * (k + 1) for _ in range(n + 1)]
    dp[0][0] = 0
    
    for i in range(1, n + 1):  # 从第 1 天到第 N 天
        for l in range(k):   # 当前天结束时剩余食物数 l
            for j in range(k):   # 前一天剩余的食物数 j
                if l - j + 1 >= 0 and l - j + 1 <= k:  # 判断购买的食物数量是否合规
                    dp[i][l] = min(dp[i][l], dp[i - 1][j] + (l - j + 1) * data[i - 1])
    # print(dp)
    return dp[n][0]
    



if __name__ == "__main__":
    # Add your test cases here

    print(solution(5, 2, [1, 2, 3, 3, 2]) == 9)
  1. 初始化DP表
  • dp[i][l] 表示第 i 天结束时,剩余 l 份食物的最小花费。
  • 二位DP表总共有 n+1 行,每一行有 k+1 列(因为食物剩余量最多为 K)。
  • 初始化为无穷大(float('inf')),表示尚未计算或状态不可达。
  • 边界条件dp[0][0] = 0表示第 0 天,没有开始旅程,无需花费,也没有食物剩余。
  1. 外层循环for i in range(1, n + 1)
  • 依次计算从第 1 天到第 n 天的最小花费。
  • 每一天的决策都依赖于前一天的状态。
  1. 中间循环for l in range(k)
  • 遍历第 i 天结束时剩余的食物数量 l,取值范围是 [0, K)
  • 目的:找到剩余 l 份食物的最小花费。
  1. 内层循环for j in range(k)
  • 遍历第 i-1 天结束时剩余的食物数量 j
  • 目的:计算从前一天 j 份食物到当天剩余 l 份食物所需的花费。
  1. 状态转移
if l - j + 1 >= 0 and l - j + 1 <= k:
    dp[i][l] = min(dp[i][l], dp[i - 1][j] + (l - j + 1) * data[i - 1])
核心判断条件
  • l - j + 1 >= 0: 表示需要购买的食物数量为 l - j + 1,不能为负数(即不能从未来的状态推导回来)。
  • l - j + 1 <= k: 表示购买的食物数量不能超过携带上限 K
转移方程
  • dp[i][l] = min(dp[i][l], dp[i - 1][j] + (l - j + 1) * data[i - 1])
    • 从前一天的状态 dp[i-1][j] 转移到今天的状态 dp[i][l]
    • 转移的花费为
      • 前一天的花费 dp[i-1][j]
      • 购买 l - j + 1 份食物的额外花费:(l - j + 1) * data[i-1](第 i-1 天的补给站食物单价为 data[i-1])。
转移结果
  • 动态更新 dp[i][l],保证对于每一天结束时剩余 l 份食物,始终保持最小花费。
  1. 输出结果

最终输出 dp[n][0],表示第 n 天结束时,无剩余食物(0 份)的最小花费。

代码逻辑验证

输入示例

n = 3  # 总共3天
k = 2  # 最多携带2份食物
data = [2, 3, 1]  # 每天的食物单价

初始状态

dp = [
    [0, inf, inf],  # 第0天:无花费
    [inf, inf, inf],  # 第1天
    [inf, inf, inf],  # 第2天
    [inf, inf, inf]   # 第3天
]

第1天:

剩余 l = 0,购买1份(消耗当天需要的):

dp[1][0] = dp[0][0] + 1 * 2 = 2

剩余 l = 1,购买2份(1份当天需要,1份备用):

dp[1][1] = dp[0][0] + 2 * 2 = 4

第2天

剩余 l = 0

dp[2][0] = min(dp[1][0] + 1 * 3, dp[1][1] + 0 * 3) = 5

剩余 l = 1:

dp[2][1] = min(dp[1][0] + 2 * 3, dp[1][1] + 1 * 3) = 7

第3天

剩余 l = 0:

dp[3][0] = min(dp[2][0] + 1 * 1, dp[2][1] + 0 * 1) = 6

最终 dp

dp = [
    [0, inf, inf],
    [2, 4, inf],
    [5, 7, inf],
    [6, inf, inf]
]

输出结果:6

总结

1. 动态规划的核心思想

  • 递归子问题:旅行第 i 天的决策依赖于前一天的状态以及当前的购买策略。
  • 最优子结构:如果前 i-1 天的决策已经是最优,则第 i 天在其基础上作出的决策也是最优。
  • 重叠子问题:第 i 天的计算需要多次重复利用前一天的状态,因此可以用动态规划存储中间结果。
  1. 二维 DP 的状态设计
  • 将状态设计为二维数组 dp[i][l],通过两个维度描述问题的不同变量:

    • 天数(时间维度)。
    • 剩余食物数量(背包维度)。
  1. 状态转移与条件约束
  • 状态转移时,通过明确的边界条件控制合理性:

    • 剩余食物不能超过最大携带量。
    • 每天至少消耗1份食物。
  • 用条件约束减少非法状态的计算,提升算法效率。

  1. 最小值优化问题的思想
  • 动态规划常用于求解最值问题(最小值或最大值)。这里通过多次迭代更新 dp 表,最终得到全局最优解。