P10726 [GESP202406 八级] 空间跳跃

GESP8 冲刺课堂页:先建模,再压缩状态,最后读代码。

一、先回答问题

结论

把每块挡板的左右端点看成图上的状态点,在状态图上求从起点到目标挡板的最短时间。

容易误入的路

只按高度贪心往下掉,可能错过需要先在挡板上横向移动的更短路线。

二、核心观察

建模与压缩

状态是“在哪块挡板的哪一端”;边分为板内横走和从端点竖直掉落。

为什么不丢答案:每一次真实移动都能对应状态图中的一条边,反过来状态图路径也都能实际执行。

端点状态 掉落目标 最短时间

三、互动演示

步骤1
关键状态 A-
关键状态 B-
答案/目标-
复杂度看表
课堂动作手推

四、自己选答案

问题 1

为什么要把左右端点分开?

问题 2

到达 t 挡板时还要走到端点吗?

五、手推结果

边类型代价作用
板内横走r-l左右端点互达
竖直掉落高度差落到下方挡板
目标判定到 t更新答案

课堂要求:每题先说清对象、关系、保留的信息、压缩掉的信息,再写代码。

六、C++14 参考代码

#include <bits/stdc++.h>
using namespace std;

const long long INF = 1e18;

struct Board {
    int id;
    int l, r, h;
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, s, t;
    cin >> n >> s >> t;
    vector<Board> b(n + 1);
    for (int i = 1; i <= n; i++) {
        b[i].id = i;
        cin >> b[i].l >> b[i].r >> b[i].h;
    }

    if (s == t) {
        cout << 0 << endl;
        return 0;
    }

    // dist[i][0] 表示到达第 i 个挡板左端点的最短时间
    // dist[i][1] 表示到达第 i 个挡板右端点的最短时间
    vector<vector<long long>> dist(n + 1, vector<long long>(2, INF));
    priority_queue<pair<long long, pair<int, int>>, vector<pair<long long, pair<int, int>>>, greater<pair<long long, pair<int, int>>>> pq;

    dist[s][0] = 0;
    pq.push({0, {s, 0}});

    long long min_to_t = INF;

    while (!pq.empty()) {
        auto curr = pq.top();
        pq.pop();
        long long d = curr.first;
        int u = curr.second.first;
        int side = curr.second.second; // 0: left, 1: right

        if (d > dist[u][side]) continue;

        // 在挡板内移动到另一端
        int other_side = 1 - side;
        long long move_cost = b[u].r - b[u].l;
        if (dist[u][other_side] > d + move_cost) {
            dist[u][other_side] = d + move_cost;
            pq.push({dist[u][other_side], {u, other_side}});
        }

        // 掉落
        int x = (side == 0 ? b[u].l : b[u].r);
        int target_board = -1;
        int max_h = -1;
        for (int i = 1; i <= n; i++) {
            if (b[i].h < b[u].h && x >= b[i].l && x <= b[i].r) {
                if (b[i].h > max_h) {
                    max_h = b[i].h;
                    target_board = i;
                }
            }
        }

        if (target_board != -1) {
            long long fall_dist = b[u].h - b[target_board].h;
            if (target_board == t) {
                min_to_t = min(min_to_t, d + fall_dist);
            } else {
                // 掉到 target_board,可以选择去它的左端点或右端点
                long long to_l = d + fall_dist + (x - b[target_board].l);
                if (dist[target_board][0] > to_l) {
                    dist[target_board][0] = to_l;
                    pq.push({dist[target_board][0], {target_board, 0}});
                }
                long long to_r = d + fall_dist + (b[target_board].r - x);
                if (dist[target_board][1] > to_r) {
                    dist[target_board][1] = to_r;
                    pq.push({dist[target_board][1], {target_board, 1}});
                }
            }
        }
    }

    if (min_to_t == INF) cout << -1 << endl;
    else cout << min_to_t << endl;

    return 0;
}