[USACO05MAR] Checking an Alibi 不在场的证明

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

所有奶牛都要判断能否在 M 秒内到达同一个目标点 1,所以只需从 1 号草地做一次 Dijkstra,再按奶牛编号检查距离是否不超过 M。

OJ: luogu

题目 ID: P6770

难度:普及/提高-

标签:最短路图论

日期: 2026-06-20 03:49

题意

农场有 F 片草地,1 号草地上是被盗的谷仓。
卫星拍下了偷窃前 M 秒时,每头奶牛所在的位置。

如果某头奶牛能在 M 秒内从照片中的位置赶到 1 号草地,那它就有作案嫌疑。

要求输出:

  1. 有嫌疑的奶牛数量
  2. 这些奶牛的编号(按输入顺序编号,从 1 开始)

思路

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

cpp
// brute.cpp:用 Floyd 求 1 号草地到所有点的最短路,再检查每头牛是否能在 M 秒内到达。
// 只适合小数据,但更贴近题意。
#include <bits/stdc++.h>
using namespace std;

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

int f, p, c, m_limit;
int cow_pos[105];
long long dist_arr[MAXF][MAXF];

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

    cin >> f >> p >> c >> m_limit;

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

    for (int i = 1; i <= p; 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 i = 1; i <= c; i++) {
        cin >> cow_pos[i];
    }

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

    vector<int> answer;
    for (int i = 1; i <= c; i++) {
        if (dist_arr[1][cow_pos[i]] <= m_limit) {
            answer.push_back(i);
        }
    }

    cout << answer.size() << '\n';
    for (size_t i = 0; i < answer.size(); i++) {
        cout << answer[i] << '\n';
    }

    return 0;
}

暴力做法是 Floyd:

  1. 先求任意两点最短路
  2. 再看每头奶牛所在位置到 1 号草地的距离是否不超过 M

这个做法能帮助理解题意,但这题并不需要全源最短路。

关键观察很简单:

  • 所有奶牛都要判断“能不能到同一个点 1

也就是说,真正需要的是:

  • 1 号草地到所有草地的最短路

因为图是无向图,所以:

  • dist(cow_pos,1)=dist(1,cow_pos)dist(cow\_pos, 1) = dist(1, cow\_pos)

这样只要从 1 号草地做一次 Dijkstra,就能得到每头奶牛需要的答案。

流程就是:

  1. 建无向带权图
  2. 1 号点做一次单源最短路
  3. 顺序检查每头奶牛所在位置 cow_pos[i]
  4. 如果 dist[cow_pos[i]] <= M,就把这头奶牛编号加入答案

代码

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

const int MAXF = 500 + 5;
const int MAXP = 1000 * 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 f, p, c, m_limit;
int head[MAXF], to[MAXP], nxt[MAXP], w[MAXP], edge_cnt;
int cow_pos[105];
long long dist_arr[MAXF];
bool vis[MAXF];

void init_graph() {
    edge_cnt = 0;
    for (int i = 1; i <= f; 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 <= f; 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 >> f >> p >> c >> m_limit;
    init_graph();

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

    for (int i = 1; i <= c; i++) {
        cin >> cow_pos[i];
    }

    // 1 号草地是被盗谷仓,只需要从这里做一次单源最短路。
    dijkstra(1);

    vector<int> answer;
    for (int i = 1; i <= c; i++) {
        if (dist_arr[cow_pos[i]] <= m_limit) {
            answer.push_back(i);
        }
    }

    cout << answer.size() << '\n';
    for (size_t i = 0; i < answer.size(); i++) {
        cout << answer[i] << '\n';
    }

    return 0;
}

复杂度

一次堆优化 Dijkstra:

  • O((F+P)logF)O((F + P) \log F)

再顺序检查所有奶牛:

  • O(C)O(C)

总复杂度:

  • O((F+P)logF+C)O((F + P) \log F + C)

空间复杂度:

  • O(F+P)O(F + P)

总结

这题的重点不是最短路模板本身,而是先看清楚查询结构:

  • 所有牛都在问“能不能到同一个目标点”

一旦发现这一点,就没必要对每头牛单独跑最短路。
直接做一次以目标点为源点的单源最短路即可。

一图流解析

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

一图流解析