[USACO10FEB] Chocolate Giving S

GitHub跳转原题关系图返回列表

路线被强制经过 1 号牧场,所以先从 1 号点跑一次 Dijkstra,任意询问答案都是 dist[p] + dist[q]。

OJ: luogu

题目 ID: P2984

难度:普及/提高-

标签:最短路图论

日期: 2026-06-20 03:17

题意

给一张带正边权的无向图,1 号牧场是仓库。

每个询问给出一头公牛所在的牧场 pp,以及它想送巧克力的母牛所在牧场 qq

这头公牛必须先到 1 号牧场拿巧克力,再从 1 号牧场走到 qq

要求输出每个询问的最短总路程。

思路

先看一个最直接的小数据暴力:

cpp
// brute.cpp:Floyd 求任意两点最短路,再回答每个询问。
// 只适合小数据,但逻辑最直接。
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 205;
const long long INF = (1LL << 60);

int n, m, b;
long long dist_arr[MAXN][MAXN];

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

    cin >> n >> m >> b;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (i == j) {
                dist_arr[i][j] = 0;
            }
            else {
                dist_arr[i][j] = INF;
            }
        }
    }

    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        if (w < dist_arr[u][v]) {
            dist_arr[u][v] = w;
            dist_arr[v][u] = w;
        }
    }

    for (int k = 1; k <= n; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (dist_arr[i][k] + dist_arr[k][j] < dist_arr[i][j]) {
                    dist_arr[i][j] = dist_arr[i][k] + dist_arr[k][j];
                }
            }
        }
    }

    while (b--) {
        int p, q;
        cin >> p >> q;
        cout << dist_arr[p][1] + dist_arr[1][q] << '\n';
    }

    return 0;
}

暴力做法是 Floyd:

  1. 先求任意两点最短路
  2. 每个询问直接输出 dist[p][1]+dist[1][q]dist[p][1] + dist[1][q]

这个做法很好理解,但这题真正的关键观察非常短:

  • 路线被强制拆成 p>1p -> 11>q1 -> q

也就是说,询问之间的公共部分就是:

  • 所有答案都要用到“从 1 号点到其他所有点的最短路”

于是只要:

  1. 1 号点跑一次 Dijkstra
  2. 记下 dist[x]dist[x] 表示 1>x1 -> x 的最短距离
  3. 对每个询问输出 dist[p]+dist[q]dist[p] + dist[q]

因为图是无向图,所以:

  • dist(p,1)=dist(1,p)dist(p, 1) = dist(1, p)
  • dist(1, q) 也已经在同一次最短路里求出来了

所以整题只需要一次单源最短路。

代码

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

const int MAXN = 50000 + 5;
const int MAXM = 100000 * 2 + 5;
const long long INF = (1LL << 60);

struct Node {
    int u;
    long long dist;

    bool operator < (const Node &other) const {
        return dist > other.dist;
    }
};

int n, m, b;
int head[MAXN], to[MAXM], nxt[MAXM], weight_arr[MAXM], edge_cnt;
long long dist_arr[MAXN];
bool vis[MAXN];

void init_graph() {
    edge_cnt = 0;
    for (int i = 1; i <= n; i++) {
        head[i] = 0;
    }
}

void add_edge(int u, int v, int w) {
    edge_cnt++;
    to[edge_cnt] = v;
    weight_arr[edge_cnt] = w;
    nxt[edge_cnt] = head[u];
    head[u] = edge_cnt;
}

// 从 1 号牧场出发做单源最短路。
void dijkstra(int start) {
    for (int i = 1; i <= n; i++) {
        dist_arr[i] = INF;
        vis[i] = false;
    }

    priority_queue<Node> pq;
    dist_arr[start] = 0;
    pq.push({start, 0});

    while (!pq.empty()) {
        Node cur = pq.top();
        pq.pop();

        int u = cur.u;
        if (vis[u]) {
            continue;
        }
        vis[u] = true;

        for (int i = head[u]; i != 0; i = nxt[i]) {
            int v = to[i];
            long long nd = dist_arr[u] + weight_arr[i];
            if (nd < dist_arr[v]) {
                dist_arr[v] = nd;
                pq.push({v, nd});
            }
        }
    }
}

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

    cin >> n >> m >> b;
    init_graph();

    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        add_edge(u, v, w);
        add_edge(v, u, w);
    }

    dijkstra(1);

    while (b--) {
        int p, q;
        cin >> p >> q;

        // 路线被强制拆成:p -> 1 -> q
        // 所以答案就是 dist[p] + dist[q]。
        cout << dist_arr[p] + dist_arr[q] << '\n';
    }

    return 0;
}

复杂度

一次 Dijkstra:

  • O((N+M)logN)O((N + M) log N)

回答 BB 个询问只要顺序输出:

  • O(B)O(B)

总复杂度:

  • O((N+M)logN+B)O((N + M) log N + B)

空间复杂度:

  • O(N+M)O(N + M)

总结

这题是一个非常标准的“先看清路径被什么条件强制拆开”的题。

一旦发现每条路线都必须经过 1,问题就不再是“很多次最短路查询”,而是:

  • 先从 1 跑一次单源最短路
  • 再把每个询问拆成两段距离直接相加

所以本质上仍然是单源最短路模板题。

一图流解析

这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。

一图流解析