P11966 [GESP202503 八级] 上学

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

一、先回答问题

结论

把学校当作唯一源点,先跑一次堆优化 Dijkstra,再用 dist[h] 回答所有同学。

容易误入的路

每来一个同学就从家重新跑最短路,会把 q 次查询放大成 q 次 Dijkstra。

二、核心观察

建模与压缩

点是路口,边是道路长度;学校 s 是源点,dist[u] 表示 u 到学校的最短时间。

为什么不丢答案:无向图中从 s 到 h 的距离等于从 h 到 s 的距离,所以一次单源最短路覆盖所有查询。

源点 s 小根堆 dist[h]

三、互动演示

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

四、自己选答案

问题 1

为什么从学校出发也能回答同学到学校?

问题 2

为什么不能用 Floyd?

五、手推结果

步骤保留的信息压缩掉的信息
建图邻接表完整矩阵
Dijkstra每点最短 dist每条候选路径
回答dist[h]重新搜索过程

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

六、C++14 参考代码

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

typedef long long ll;
const ll INF = 1e18;

struct Edge {
    int to;
    int weight;
};

struct Node {
    int id;
    ll dist;
    bool operator>(const Node& other) const {
        return dist > other.dist;
    }
};

int main() {
    // 加速输入输出
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n, m, s, q;
    cin >> n >> m >> s >> q;
    
    vector<vector<Edge>> adj(n + 1);
    for (int i = 0; i < m; i++) {
        int u, v, l;
        cin >> u >> v >> l;
        adj[u].push_back({v, l});
        adj[v].push_back({u, l});
    }
    
    // Dijkstra 算法
    vector<ll> dist(n + 1, INF);
    priority_queue<Node, vector<Node>, greater<Node>> pq;
    
    dist[s] = 0;
    pq.push({s, 0});
    
    while (!pq.empty()) {
        Node current = pq.top();
        pq.pop();
        
        int u = current.id;
        ll d = current.dist;
        
        if (d > dist[u]) continue;
        
        for (auto& edge : adj[u]) {
            int v = edge.to;
            int weight = edge.weight;
            if (dist[u] + weight < dist[v]) {
                dist[v] = dist[u] + weight;
                pq.push({v, dist[v]});
            }
        }
    }
    
    // 处理查询
    for (int i = 0; i < q; i++) {
        int h;
        cin >> h;
        cout << dist[h] << "\n";
    }
    
    return 0;
}