徒步旅行最小食物花费问题详解
问题描述
小R需要在N天的徒步旅行中每天消耗1份食物,每天可在补给站购买食物。限制条件:
- 购买后携带量 ≤ K
- 必须确保当天有食物可用
求完成旅行的最小花费
输入输出
输入:
- n:总天数
- k:最大携带量
- data[]:每天的食物价格
输出:
- 完成旅行的最小花费
核心思路
关键观察
- 价格窗口期:当某天价格是后续区间内的最低价时,应尽可能多买
- 库存管理:需要跟踪剩余食物量来避免重复购买
- 动态规划状态:
dp[i][f]表示第i天开始时有f份食物的最小花费
算法框架
- 预处理:使用单调栈找到每个位置右侧第一个更低价格的位置
- 动态规划:维护库存状态,计算最优购买策略
详细解法步骤
步骤1:预处理Next Lower数组
int[] nextLower = new int[n];
Deque<Integer> stack = new ArrayDeque<>();
for (int i = n-1; i >= 0; i--) {
while (!stack.isEmpty() && data[stack.peek()] >= data[i]) {
stack.pop();
}
nextLower[i] = stack.isEmpty() ? n : stack.peek();
stack.push(i);
}
- 目的:快速查询后续更优购买时机
- 时间复杂度:O(n)
- 示例:data = [1,2,3,3,2] → nextLower = [5,4,4,4,5]
步骤2:动态规划实现
状态定义
dp[i][f]:第i天开始时有f份食物的最小累计花费
初始化
int[][] dp = new int[n+1][k+1];
for (int[] row : dp) Arrays.fill(row, Integer.MAX_VALUE/2);
dp[0][0] = 0; // 初始状态
状态转移
for (int i = 0; i < n; i++) {
for (int f = 0; f <= k; f++) {
if (dp[i][f] == Integer.MAX_VALUE/2) continue;
// 计算购买范围
int m = nextLower[i];
int maxCover = m - i;
int maxBuy = Math.min(k - f, maxCover);
int minBuy = Math.max(1 - f, 0);
// 遍历所有合法购买量
for (int buy = minBuy; buy <= maxBuy; buy++) {
int newF = f + buy - 1; // 次日库存
if (newF < 0 || newF > k-1) continue;
dp[i+1][newF] = Math.min(dp[i+1][newF],
dp[i][f] + buy * data[i]);
}
}
}
步骤3:获取结果
return dp[n][0]; // 最终必须消耗完所有食物
正确性证明
- 最优子结构:每个状态的最优解由前序状态的最优解转移而来
- 无后效性:次日的状态只与当前状态有关
- 完全覆盖:遍历所有可能的购买量和库存状态
复杂度分析
| 步骤 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 预处理 | O(n) | O(n) |
| 动态规划 | O(nk²) | O(nk) |
测试样例解析
样例1:n=5, k=2, data=[1,2,3,3,2]
最优路径:
Day0: 买2份(花费2) → 库存1
Day1: 消耗库存 → 库存0
Day2: 必须买1份(花费3) → 库存0
Day3: 必须买1份(花费3) → 库存0
Day4: 必须买1份(花费2) → 总花费9
DP转移关键步骤:
- Day0状态(0,0) → 买2 → 转移到(1,1)花费2
- Day1状态(1,1) → 买0 → 转移到(2,0)花费保持2
- Day2状态(2,0) → 必须买1 → 转移到(3,0)花费+3=5
- ...最终得到dp[5][0] = 9
优化技巧
- 滚动数组:空间复杂度可优化为O(k)
- 剪枝优化:跳过不可能达到的状态
- 提前终止:当后续所有价格更高时直接购买所需量
常见错误分析
-
忽略当天消耗:购买后要立即减1份库存
-
边界条件错误:
- 最后一天必须库存为0
- 购买量需要满足1 ≤ (库存 + 购买量) ≤ k
-
贪心策略失效:单纯按最低价购买无法处理库存限制
总结
本题通过动态规划精确管理库存状态,结合单调栈预处理优化决策范围,能够正确处理所有边界情况。关键点在于:
- 预处理确定每个价格的有效区间
- 动态规划状态转移的精确计算
- 库存变化的正确处理