[USACO10DEC] Apple Delivery S

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

只需要比较两种送货顺序:PB->PA1->PA2 和 PB->PA2->PA1。图是无向图,因此求出 PB 到两点的距离和 PA1 到 PA2 的距离后即可直接取最小值。

OJ: luogu

题目 ID: P3003

难度:普及/提高-

标签:最短路图论

日期: 2026-06-20 03:53

题意

贝茜从起点 PB 出发,要给 PA1PA2 两个牧场送苹果。

她必须把两个点都访问到,但:

  • 先去 PA1 再去 PA2
  • 或先去 PA2 再去 PA1

顺序可以自己选。
图是无向带权图,要求最小总路程。

思路

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

cpp
// brute.cpp:用 Floyd 求任意两点最短路,再直接枚举两种送货顺序。
// 只适合小数据,但最贴近题意。
#include <bits/stdc++.h>
using namespace std;

const int MAXP = 105;
const long long INF = (1LL << 60);

int c, p, pb, pa1, pa2;
long long dist_arr[MAXP][MAXP];

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

    cin >> c >> p >> pb >> pa1 >> pa2;

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

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

    for (int k = 1; k <= p; k++) {
        for (int i = 1; i <= p; i++) {
            for (int j = 1; j <= p; 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];
                }
            }
        }
    }

    long long ans1 = dist_arr[pb][pa1] + dist_arr[pa1][pa2];
    long long ans2 = dist_arr[pb][pa2] + dist_arr[pa2][pa1];

    cout << min(ans1, ans2) << '\n';

    return 0;
}

暴力做法是 Floyd:

  1. 先求任意两点最短路
  2. 比较两种顺序:
    • PBPA1PA2PB \to PA1 \to PA2
    • PBPA2PA1PB \to PA2 \to PA1

这个思路已经把本题本质暴露出来了:
真正难点根本不在状态设计,而在先想明白“只有两种顺序”。

因为只有两个送货点,所以总路线只有:

  1. PBPA1PA2PB \to PA1 \to PA2
  2. PBPA2PA1PB \to PA2 \to PA1

因此只要知道这三个关键距离:

  • dist(PB,PA1)dist(PB, PA1)
  • dist(PB,PA2)dist(PB, PA2)
  • dist(PA1,PA2)dist(PA1, PA2)

答案就能直接写出来。

又因为图是无向图,所以:

  • dist(PA1,PA2)=dist(PA2,PA1)dist(PA1, PA2) = dist(PA2, PA1)

于是只需要:

  1. PB 做一次 Dijkstra,拿到 PB 到两个送货点的距离
  2. 再从 PA1 做一次 Dijkstra,拿到 PA1PA2 的距离

最后比较:

  • dist(PB,PA1)+dist(PA1,PA2)dist(PB, PA1) + dist(PA1, PA2)
  • dist(PB,PA2)+dist(PA1,PA2)dist(PB, PA2) + dist(PA1, PA2)

取更小的即可。

代码

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

const int MAXP = 100000 + 5;
const int MAXC = 200000 * 2 + 5;
const long long INF = (1LL << 60);

struct HeapNode {
    int u;
    long long dist;

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

int c, p, pb, pa1, pa2;
int head[MAXP], to[MAXC], nxt[MAXC], w[MAXC], edge_cnt;
long long dist_arr[MAXP];
bool vis[MAXP];

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

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

void dijkstra(int start) {
    for (int i = 1; i <= p; i++) {
        dist_arr[i] = INF;
        vis[i] = false;
    }

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

    while (!pq.empty()) {
        HeapNode 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] + w[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 >> c >> p >> pb >> pa1 >> pa2;
    init_graph();

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

    dijkstra(pb);
    long long d_pb_a1 = dist_arr[pa1];
    long long d_pb_a2 = dist_arr[pa2];

    dijkstra(pa1);
    long long d_a1_a2 = dist_arr[pa2];

    // 只有两种顺序:
    // 1. PB -> PA1 -> PA2
    // 2. PB -> PA2 -> PA1
    long long ans1 = d_pb_a1 + d_a1_a2;
    long long ans2 = d_pb_a2 + d_a1_a2;

    cout << min(ans1, ans2) << '\n';

    return 0;
}

复杂度

做两次堆优化 Dijkstra:

  • O((P+C)logP)O((P + C) \log P)

总复杂度:

  • O((P+C)logP)O((P + C) \log P)

空间复杂度:

  • O(P+C)O(P + C)

总结

这题最关键的不是最短路模板,而是先把路线顺序枚举清楚。

一旦发现只有两种顺序,问题就变成:

  • 求几个关键点对之间的最短路

所以它本质上是一道“枚举顺序 + 单源最短路”的组合题。

一图流解析

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

一图流解析