徒步旅行中的补给问题
问题描述
小R正在计划一次从地点A到地点B的徒步旅行,总路程需要 N 天。为了保持充足的能量,小R每天必须消耗1份食物。在旅途中,每天都会经过一个补给站,可以购买食物补充。然而,每个补给站的食物价格可能不同,并且小R最多只能携带 K 份食物。
现在,小R希望在保证每天都有食物的前提下,以最小的花费完成这次徒步旅行。你能帮助小R计算出最低的花费是多少吗?
问题分析
要解决这个问题,可以利用动态规划的思想。具体来说,我们需要为每一天(从第1天到第N天)计算出到达该天时的最小花费,并在每一天购买合适的食物份数。每次购买食物时,要保证不超过携带食物的最大限制 K,并且要考虑每个补给站的价格。
我们的目标是找到一条动态规划的路径,从第1天到第N天,确保每天消耗1份食物且花费最少。
难点
- 动态规划状态定义:如何定义状态是解决问题的关键。对于第i天,我们可以定义一个状态
dp[i],表示到达第i天所需的最小花费。 - 状态转移:在每一天,我们有多个选择(买不同数量的食物)。需要考虑从前一天到今天的转移,确保在限制内买足够的食物。
心路历程
嘿嘿,这题卡了我不少时间。在最初的解法中,我使用了动态规划的思想,但是状态转移的逻辑存在问题。具体来说,我错误地假设每次购买的食物数量可以在每天的最大携带量K内自由选择,而忽略了“补给站价格”的影响。我最初的代码使用了错误的状态转移方式,导致无法正确计算每一天的最小花费。
在向师兄请教后,得到了正确的状态转移方式。师兄指出,我们需要动态地决定每一天购买多少份食物,购买数量不仅受限于当天的最大携带量,还要考虑之前买的食物数量和补给站的价格。
修正后的代码实现
在修正后的代码中,我使用了递归和记忆化的方法,通过 memo 数组来存储已经计算过的状态,避免重复计算。每次从第i天开始,我们根据当前背包剩余食物的数量来决定购买多少份食物,并递归计算下一天的花费。
def solution(n, k, data):
# memo数组,用来存储已经计算的结果
memo = [[-1] * (k + 1) for _ in range(n)]
def dfs(i, bag):
# 基本条件:到达最后一天,无需再花费
if i == n:
return 0
# 如果已经计算过此状态,直接返回结果
if memo[i][bag] != -1:
return memo[i][bag]
ans = float('inf') # 初始答案为无穷大
# 决定购买多少份食物
j = 1 if bag == 0 else 0 # 如果没有食物,必须购买至少1份
for j in range(j, k - bag + 1):
# 计算从i到i+1需要购买的食物花费
current_cost = j * data[i] + dfs(i + 1, bag + j - 1)
ans = min(ans, current_cost) # 更新最小花费
# 记忆化存储当前状态的最小花费
memo[i][bag] = ans
return ans
return dfs(0, 0)
# 测试用例
if __name__ == "__main__":
print(solution(5, 2, [1, 2, 3, 3, 2]) == 9) # 期望输出 True
print(solution(6, 3, [4, 1, 5, 2, 1, 3]) == 9) # 期望输出 True
print(solution(4, 1, [3, 2, 4, 1]) == 10) # 期望输出 True
代码讲解
- 递归函数:
dfs(i, bag)表示从第i天开始,当前背包内有bag份食物时,剩余旅程所需的最小花费。 - 记忆化:
memo[i][bag]用来存储从第i天,背包内有bag份食物时的最小花费,避免重复计算。 - 购买食物:每一天,我们根据剩余食物数量
bag决定买多少份食物。买的数量限制在剩余最大可携带的食物数k - bag之间。 - 最小化花费:在每一天的决策中,我们尝试每一种购买食物的方式,并计算出最小的花费。
测试样例分析
- 样例1:输入
(5, 2, [1, 2, 3, 3, 2]),经过计算,最低花费是9。 - 样例2:输入
(6, 3, [4, 1, 5, 2, 1, 3]),经过计算,最低花费是9。 - 样例3:输入
(4, 1, [3, 2, 4, 1]),经过计算,最低花费是10。
总结
这道题的关键在于如何有效地使用动态规划和记忆化来解决最小化花费问题。通过合理定义状态并进行递归搜索,结合记忆化避免重复计算,我们能够高效地解决问题。