徒步旅行最小食物花费问题详解

87 阅读3分钟

徒步旅行最小食物花费问题详解

问题描述

小R需要在N天的徒步旅行中每天消耗1份食物,每天可在补给站购买食物。限制条件:

  1. 购买后携带量 ≤ K
  2. 必须确保当天有食物可用
    求完成旅行的最小花费

输入输出

输入

  • n:总天数
  • k:最大携带量
  • data[]:每天的食物价格

输出

  • 完成旅行的最小花费

核心思路

关键观察

  1. 价格窗口期:当某天价格是后续区间内的最低价时,应尽可能多买
  2. 库存管理:需要跟踪剩余食物量来避免重复购买
  3. 动态规划状态dp[i][f]表示第i天开始时有f份食物的最小花费

算法框架

  1. 预处理:使用单调栈找到每个位置右侧第一个更低价格的位置
  2. 动态规划:维护库存状态,计算最优购买策略

详细解法步骤

步骤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]; // 最终必须消耗完所有食物

正确性证明

  1. 最优子结构:每个状态的最优解由前序状态的最优解转移而来
  2. 无后效性:次日的状态只与当前状态有关
  3. 完全覆盖:遍历所有可能的购买量和库存状态

复杂度分析

步骤时间复杂度空间复杂度
预处理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

优化技巧

  1. 滚动数组:空间复杂度可优化为O(k)
  2. 剪枝优化:跳过不可能达到的状态
  3. 提前终止:当后续所有价格更高时直接购买所需量

常见错误分析

  1. 忽略当天消耗:购买后要立即减1份库存

  2. 边界条件错误

    • 最后一天必须库存为0
    • 购买量需要满足1 ≤ (库存 + 购买量) ≤ k
  3. 贪心策略失效:单纯按最低价购买无法处理库存限制

总结

本题通过动态规划精确管理库存状态,结合单调栈预处理优化决策范围,能够正确处理所有边界情况。关键点在于:

  1. 预处理确定每个价格的有效区间
  2. 动态规划状态转移的精确计算
  3. 库存变化的正确处理