题解:AtCoder AT_awc0033_e Minimum Cost of Stepping Stones

【题目来源】

AtCoder:E - Minimum Cost of Stepping Stones

【题目描述】

Takahashi is trying to cross a river. There are \(N\) stepping stones lined up in a row in the river, numbered stone \(1\), stone \(2\), \(\ldots\), stone \(N\) in order from the left bank. Takahashi starts on stone \(1\) and reaches the goal when he arrives at stone \(N\).
高桥正试图渡过一条河。河中有 \(N\) 块踏脚石排成一行,从左岸开始依次编号为石头 \(1\)、石头 \(2\)、……、石头 \(N\)。高桥从石头 \(1\) 出发,当他到达石头 \(N\) 时即到达目标。

From his current stone \(i\), Takahashi can jump to any one of stone \(i+1\), stone \(i+2\), \(\ldots\), stone \(\min(i+K,\, N)\). In other words, the difference in stone numbers covered in a single jump is between \(1\) and \(K\) inclusive, and he can only move in the direction of increasing numbers. Takahashi must land exactly on stone \(N\) at the end.
从当前所在的石头 \(i\),高桥可以跳到石头 \(i+1\)、石头 \(i+2\)、……、石头 \(\min(i+K,\, N)\) 中的任意一块。换句话说,单次跳跃跨越的石头编号差在 \(1\)\(K\) 之间(含端点),且他只能朝编号增加的方向移动。高桥最终必须恰好落在石头 \(N\) 上。

Each stepping stone \(i\) \((1 \leq i \leq N)\) has a positive integer value \(A_i\) assigned to it. When Takahashi lands on a stone, that stone's value is added to the cumulative cost. The values of stones he does not land on are not added. The value \(A_1\) of the starting stone \(1\) and the value \(A_N\) of the goal stone \(N\) are also each added when he lands on them.
每块踏脚石 \(i\)\(1 \leq i \leq N\))都有一个正整数 \(A_i\) 作为其值。当高桥落在一块石头上时,该石头的值会被累加到累计成本中。他没有踩到的石头的值不会被累加。起始石头 \(1\) 的值 \(A_1\) 和目标石头 \(N\) 的值 \(A_N\) 也会在他落在上面时分别被累加。

Among all paths Takahashi can take from stone \(1\) to stone \(N\), find the one that minimizes the cumulative cost, and output that minimum cumulative cost.
在高桥从石头 \(1\) 到石头 \(N\) 的所有可行路径中,找出使累计成本最小化的路径,并输出该最小累计成本。

【输入】

\(N\) \(K\)
\(A_1\) \(A_2\) \(\ldots\) \(A_N\)

  • The first line contains an integer \(N\) representing the number of stepping stones and an integer \(K\) representing the maximum difference in stone numbers that can be covered in a single jump, separated by a space.
  • The second line contains integers \(A_1, A_2, \ldots, A_N\) representing the values of each stepping stone, separated by spaces.

【输出】

Output in one line the minimum cumulative cost when traveling from stone \(1\) to stone \(N\).

【输入样例】

5 2
1 3 2 4 1

【输出样例】

4

【核心思想】

  1. 问题分析:给定 \(N\) 块踏脚石,每块石头 \(i\) 有价值 \(A_i\)。从石头 \(1\) 出发,每次可跳 \(1\)\(K\) 步,到达石头 \(N\) 时累加所有经过石头的价值,求最小累计成本。这是一个动态规划 + 单调队列优化问题,关键在于高效求解滑动窗口内的最小值。

  2. 算法选择

    • 动态规划(DP)dp[i] 表示到达石头 \(i\) 的最小累计成本
    • 单调队列优化:将转移方程 dp[i] = min{dp[j]} + A[i](其中 \(i-K \leq j < i\))的复杂度从 \(O(N \times K)\) 优化到 \(O(N)\)
    • 滑动窗口最小值:单调队列维护窗口 \([i-K, i-1]\) 内的最小 \(dp\)
  3. 关键步骤

    • 初始化
      • 读取 \(N\)(石头数量)、\(K\)(最大跳跃距离)、\(A[1..N]\)(每块石头的价值)
      • dp[1] = A[1],其余 dp[i] = INF
      • {dp[1], 1} 加入单调队列
    • DP 转移(遍历 \(i\)\(2\)\(N\)):
      • 移除过期元素:当队首元素下标 < i - K 时,弹出队首
      • 状态转移dp[i] = dq.front().v + A[i](取窗口内最小 \(dp\) 值加上当前石头价值)
      • 维护单调性:从队尾弹出所有值 >= dp[i] 的元素,保持队列递增
      • 入队:将 {dp[i], i} 加入队尾
    • 输出答案dp[N] 即为到达石头 \(N\) 的最小累计成本
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),每个元素最多入队和出队各一次
    • 空间复杂度:\(O(N)\)\(dp\) 数组和单调队列的空间
  5. 单调队列优化 DP 的核心思想

    • 滑动窗口最值:单调队列在 \(O(1)\) 时间内获取窗口内的最小值,避免每次遍历窗口
    • 过期元素处理:当窗口滑动时,及时移除下标超出范围的元素
    • 单调性维护:新元素入队前,从队尾移除所有不优的元素,保证队首始终最优
    • DP 优化范式:对于形如 dp[i] = min/max{dp[j]} + cost[i]\(j\) 在滑动窗口内)的转移,均可使用单调队列优化
    • 适用于区间最值 DP、滑动窗口最值类问题

【解题思路】

【算法标签】

单调队列

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1000005, INF = 1e18;
int n, k;
int a[N], dp[N];  // a: 代价数组, dp: 动态规划数组
struct Node
{
    int v, idx;  // v: 值, idx: 索引
};
deque<Node> dq;  // 单调队列

signed main()
{
    scanf("%d %d", &n, &k);  // 输入数组长度和跳跃距离限制
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);  // 输入每个位置的代价
    }

    for (int i = 1; i <= n; i++)  // 初始化dp数组
    {
        dp[i] = INF;
    }
    dp[1] = a[1];  // 起点代价
    dq.push_back({dp[1], 1});  // 将起点加入队列
    for (int i = 2; i <= n; i++)
    {
        // 移除超出窗口范围的元素
        while (!dq.empty() && dq.front().idx < i - k)
        {
            dq.pop_front();
        }
        // 转移方程:dp[i] = min{dp[j]} + a[i] (i-k ≤ j < i)
        dp[i] = dq.front().v + a[i];

        // 维护队列单调递增
        while (!dq.empty() && dq.back().v >= dp[i])
        {
            dq.pop_back();
        }
        dq.push_back({dp[i], i});  // 将当前元素加入队列
    }

    cout << dp[n] << endl;  // 输出到达终点的最小总代价
    return 0;
}

【运行结果】

5 2
1 3 2 4 1
posted @ 2026-06-15 09:49  团爸讲算法  阅读(14)  评论(0)    收藏  举报