[USACO2.4] 回家 Bessie Come Home

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

牧场总数只有 52 个,把大小写字母映射成编号后直接 Floyd 求全源最短路,再在 A..Y 中找离 Z 最近的那头牛。

OJ: luogu

题目 ID: P1529

难度:普及-

标签:最短路图论Floyd

日期: 2026-06-20 03:29

题意

牧场用字母表示:

  • a..z
  • A..Y
  • Z

其中:

  • 大写 A..Y 上各有一头牛
  • Z 是谷仓
  • 小写字母上没有牛

给若干条无向边和边权。
所有牛都要走最短路回到 Z

要求输出:

  1. 最先回到谷仓的那头牛所在的大写牧场字母
  2. 它到 Z 的最短路长度

思路

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

cpp
// brute.cpp:对每头有牛的大写牧场各跑一次 Dijkstra,直接比较谁到 Z 最近。
// 小数据下很好理解,也方便对拍。
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 52;
const int MAXM = 4005;
const int INF = 1e9;

struct HeapNode {
    int u;
    int dist;

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

int p;
int head[MAXN], to[MAXM], nxt[MAXM], w[MAXM], edge_cnt;
int dist_arr[MAXN];
bool vis[MAXN];

int char_to_id(char ch) {
    if ('a' <= ch && ch <= 'z') {
        return ch - 'a';
    }
    return ch - 'A' + 26;
}

char id_to_char(int id) {
    if (id < 26) {
        return char('a' + id);
    }
    return char('A' + (id - 26));
}

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 = 0; i < MAXN; 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];
            int 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 >> p;

    for (int i = 0; i < MAXN; i++) {
        head[i] = 0;
    }
    edge_cnt = 0;

    for (int i = 1; i <= p; i++) {
        char a, b;
        int len;
        cin >> a >> b >> len;

        int u = char_to_id(a);
        int v = char_to_id(b);
        add_edge(u, v, len);
        add_edge(v, u, len);
    }

    int z_id = char_to_id('Z');
    int answer_id = -1;
    int answer_dist = INF;

    for (int i = char_to_id('A'); i < z_id; i++) {
        dijkstra(i);
        if (dist_arr[z_id] < answer_dist) {
            answer_dist = dist_arr[z_id];
            answer_id = i;
        }
    }

    cout << id_to_char(answer_id) << ' ' << answer_dist << '\n';

    return 0;
}

暴力做法是:

  1. 对每个大写牧场 A..Y 单独跑一次 Dijkstra
  2. 看谁到 Z 最近

这个做法已经可以过小数据,也很好理解。

但这题最关键的观察是:

  • 整张图的点其实只有 26 + 26 = 52

也就是说,虽然输入看起来像字符串图,但本质上只是一个很小的图。
这时直接做 Floyd 反而最自然:

  1. 先把字符映射成 0..51 的编号
  2. 建一个 52 x 52 的距离矩阵
  3. Floyd 求任意两点最短路
  4. 枚举所有有牛的大写点 A..Y,找 dist[i][Z] 最小的那个

这里要注意两点:

1. 大小写是不同的点

mM 不是同一个牧场,所以必须分开编号。

2. 只有 A..Y 上有牛

Z 是谷仓,没有牛在 Z 上。
所以最后枚举答案时只看:

  • AY

不看 Z

代码

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

const int MAXN = 52;
const int INF = 1e9;

int p;
int dist_arr[MAXN][MAXN];

int char_to_id(char ch) {
    if ('a' <= ch && ch <= 'z') {
        return ch - 'a';
    }
    return ch - 'A' + 26;
}

char id_to_char(int id) {
    if (id < 26) {
        return char('a' + id);
    }
    return char('A' + (id - 26));
}

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

    cin >> p;

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

    for (int i = 1; i <= p; i++) {
        char a, b;
        int w;
        cin >> a >> b >> w;

        int u = char_to_id(a);
        int v = char_to_id(b);

        if (w < dist_arr[u][v]) {
            dist_arr[u][v] = w;
            dist_arr[v][u] = w;
        }
    }

    // 点数只有 52,直接 Floyd 求任意两点最短路最省事。
    for (int k = 0; k < MAXN; k++) {
        for (int i = 0; i < MAXN; i++) {
            for (int j = 0; j < MAXN; 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];
                }
            }
        }
    }

    int z_id = char_to_id('Z');
    int answer_id = -1;
    int answer_dist = INF;

    // 只有 A..Y 上有牛,Z 是谷仓,不参与比较。
    for (int i = char_to_id('A'); i < z_id; i++) {
        if (dist_arr[i][z_id] < answer_dist) {
            answer_dist = dist_arr[i][z_id];
            answer_id = i;
        }
    }

    cout << id_to_char(answer_id) << ' ' << answer_dist << '\n';

    return 0;
}

复杂度

Floyd 的时间复杂度:

  • O(523)O(52^3)

这在本题里就是一个很小的常数。

空间复杂度:

  • O(522)O(52^2)

总结

这题最重要的不是最短路模板本身,而是先看清楚:

  • 点数其实只有 52

一旦意识到图很小,Floyd 就是最顺手的写法。
所以这是一个很典型的“先估点数,再选最短路算法”的题。

一图流解析

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

一图流解析