徒步旅行中的补给问题
问题背景
小R计划进行一次为期 NNN 天的徒步旅行。在旅行过程中,小R每天都需要消耗 1 份食物来维持体力。幸运的是,小R每天都会经过一个补给站,可以购买食物补充能量。然而,补给站的食物价格每天不同,同时为了方便携带和减轻负担,小R最多只能携带 KKK 份食物。为了让整个旅行的花费尽可能少,小R需要设计一个合理的购买计划。
问题目标
在保证每天都有足够食物的前提下,计算出小R完成整个旅行的最低总花费。
解题思路
这个问题的核心在于优化每日的购买和携带策略,确保花费最少的同时满足每日的食物需求。基于这一点,可以使用动态规划(Dynamic Programming,简称 DP)来解决。动态规划可以通过记录中间状态,将问题分解为更小的子问题,从而实现整体的最优解。
动态规划设计
-
状态定义
我们定义一个二维 DP 表 dp[i][j]dp[i][j]dp[i][j],表示在第 iii 天结束时,小R携带 jjj 份食物所需的最低总花费。其中:- iii 的取值范围为 0 到 N−1N-1N−1,表示旅行的天数。
- jjj 的取值范围为 0 到 KKK,表示当天结束时小R携带的食物数量。
-
状态转移方程
第 iii 天,小R有两种选择:- 选择 1:不购买食物,只消耗前一天携带的食物。
如果在第 iii 天结束时携带 jjj 份食物,则第 i−1i-1i−1 天必须至少携带 j+1j+1j+1 份食物才能支撑今天的需求。对应的状态转移公式为: dp[i][j]=min(dp[i][j],dp[i−1][j+1])dp[i][j] = \min(dp[i][j], dp[i-1][j+1])dp[i][j]=min(dp[i][j],dp[i−1][j+1]) - 选择 2:购买食物补充携带量。
小R可以选择在第 iii 天购买 lll 份食物,并结合前一天的携带情况转移状态。如果第 iii 天结束时携带 jjj 份食物,则前一天需要携带 j−l+1j-l+1j−l+1 份,并购买 lll 份。对应的公式为: dp[i][j]=min(dp[i][j],dp[i−1][j−l+1]+l×data[i])dp[i][j] = \min(dp[i][j], dp[i-1][j-l+1] + l \times \text{data}[i])dp[i][j]=min(dp[i][j],dp[i−1][j−l+1]+l×data[i]) 其中 lll 的范围为 1 到 jjj。
- 选择 1:不购买食物,只消耗前一天携带的食物。
-
初始条件
第一天 i=0i = 0i=0 时,小R必须直接购买食物,因为没有前一天的携带量。初始状态为:dp[0][j]=data[0]×jdp[0][j] = \text{data}[0] \times jdp[0][j]=data[0]×j
其中 jjj 的范围为 0 到 KKK。
-
最终结果
旅行结束后,第 NNN 天必须携带至少 1 份食物,才能满足需求。因此,最终答案为第 N−1N-1N−1 天携带 1 到 KKK 份食物的最小花费:result=min(dp[N−1][1],dp[N−1][2],…,dp[N−1][K])\text{result} = \min(dp[N-1][1], dp[N-1][2], \dots, dp[N-1][K])result=min(dp[N−1][1],dp[N−1][2],…,dp[N−1][K])
算法复杂度分析
-
时间复杂度
每一天的 DP 状态需要遍历所有可能的携带量 KKK,并为每个状态计算所有可能的购买策略,复杂度为 O(n×k2)O(n \times k^2)O(n×k2)。 -
空间复杂度
需要一个大小为 n×kn \times kn×k 的 DP 表来存储状态,空间复杂度为 O(n×k)O(n \times k)O(n×k)。def solution(n, k, data): # 初始化 dp 表,表示在每一天以每种食物携带量的最小花费 dp = [[float('inf') for _ in range(k+1)] for _ in range(n)]` # 初始化第一天的食物购买情况 for i in range(k+1): dp[0][i] = data[0] * i # 在第一天购买 `i` 份食物的花费 # 动态规划填表 for i in range(1, n): # 遍历每一天 for j in range(k+1): # 遍历携带的食物数量 # 情况1:不购买食物,从前一天带来的食物消费1份到达今天 if j < k: dp[i][j] = min(dp[i][j], dp[i-1][j+1]) # 情况2:购买食物补给 for l in range(1, j+1): if j - l >= 0: dp[i][j] = min(dp[i][j], dp[i-1][j-l+1] + l * data[i]) # 输出 dp 表调试 print(dp)
示例分析
假设 n=3n = 3n=3、k=2k = 2k=2,食物价格数组为 data=[3,2,1]\text{data} = [3, 2, 1]data=[3,2,1]。
- 第一天:携带 0, 1, 2 份食物的花费分别为 0, 3, 6。
- 第二天:通过动态规划,计算从第一天携带过来的食物或当天补充的食物。
- 第三天:重复类似计算,最终取携带至少 1 份食物的最小值。
最终结果为最低花费 5。
总结
此问题通过动态规划巧妙地平衡了携带量和购买成本之间的关系,清晰地将复杂的选择过程分解为简单的子问题。设计的状态转移公式既合理又高效,可以扩展到不同的天数和携带限制。此外,算法复杂度在可接受范围内,适用于中等规模的数据输入。