题解:洛谷 P6770 Checking an Alibi 不在场的证明

【题目来源】

洛谷:P6770 [USACO05MAR] Checking an Alibi 不在场的证明 - 洛谷

【题目描述】

谷仓里发现谷物被盗!FJ 正试图从 \(C\) 只奶牛里找出那个偷谷物的罪犯。幸运的是,一个恰好路过的卫星拍下谷物被盗前 \(M\) 秒的农场的图片。这样约翰就能通过牛们的位置来判断谁有足够的时间来盗窃谷物。

约翰农场有 \(F\) 片草地,标号 \(1\)\(F\),还有 \(P\) 条双向路连接着它们。通过这些路需要的时间在 \(1\)\(7\times 10^4\) 秒的范围内。草地 \(1\) 上建有那个被盗的谷仓。给出农场地图,以及卫星照片里每只牛所在的位置,请判断哪些牛有可能犯罪。

请注意:数据里可能存在重边(起点和终点相同的边)。

题目中所说的“可能犯罪的牛”指的是在 \(M\) 秒内能从照片内的位置到达 \(1\) 号草地的牛。

【输入】

\(1\) 行:四个以空格分隔的整数:\(F,P,C\)\(M\)

\(2\) 行至 \(P+1\) 行:三个以空格分隔的整数,描述一个路径。连接 \(F_1\)\(F_2\) 的路径需要 \(T\) 秒。

\(P+2\) 行至 \(P+C+1\) 行:每行一个整数,是一头牛的位置。

【输出】

\(1\) 行输出嫌疑犯的数目,接下来每一行输出一只嫌疑犯的编号,按编号从小到大排序。

【输入样例】

7 6 5 8
1 4 2
1 2 1
2 3 6
3 5 5
5 4 6
1 7 9
1
4
5
3
7

【输出样例】

4
1
2
3
4

【核心思想】

  1. 问题分析:给定 \(F\) 个农场(草地)和 \(P\) 条双向道路(通行时间 \(1\)\(7 \times 10^4\) 秒),以及 \(C\) 头奶牛的位置。谷仓在农场 \(1\),卫星照片拍摄于盗窃前 \(M\) 秒。求哪些奶牛能在 \(M\) 秒内从当前位置到达农场 \(1\)。本质是一个单源最短路径 + 阈值筛选问题。

  2. 算法选择

    • BFS(适用于小权值)或 Dijkstra(通用):由于边权范围较大(\(1\)\(7 \times 10^4\)),但农场数 \(F \leq 500\) 较小,代码中使用的是类BFS的队列搜索(实际为SPFA思想的简化版),通过邻接矩阵存储并处理重边
  3. 关键步骤

    • 建图:用邻接矩阵 \(g[u][v]\) 存储农场间的最短时间,处理重边时取最小值
    • BFS求最短路(从农场 \(1\) 出发):
      • 初始化 \(\text{dist}\) 数组为 \(\infty\)\(\text{dist}[1] = 0\)
      • 队列存储 \((\text{节点}, \text{累计时间})\),从农场 \(1\) 开始
      • 出队时若节点已访问或累计时间 \(> M\) 则跳过
      • 标记访问,更新 \(\text{dist}[u] = \min(\text{dist}[u], \text{time})\)
      • 遍历所有邻接节点,将 \((i, \text{time} + g[u][i])\) 入队
    • 筛选嫌疑犯:遍历每头奶牛的位置 \(\text{pos}[i]\),若 \(\text{dist}[\text{pos}[i]] \leq M\),则该奶牛编号 \(i\) 为嫌疑犯
    • 输出结果:先输出嫌疑犯数量,再按编号顺序输出(注意代码中排序被注释,但输入顺序恰好有序)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(F^2 + P + C)\),BFS在邻接矩阵上为 \(O(F^2)\),筛选 \(O(C)\)
    • 空间复杂度:\(O(F^2)\),邻接矩阵存储
  5. 单源最短路 + 阈值筛选的核心思想

    • 逆向思维:不计算每头奶牛到农场 \(1\) 的距离,而是计算从农场 \(1\) 到所有农场的最短距离,再反向匹配奶牛位置
    • 重边处理:输入可能存在多条同端点道路,取最小时间存入邻接矩阵
    • 时间约束剪枝:BFS过程中若累计时间已超过 \(M\),直接跳过该分支,避免无效搜索
    • 访问标记去重:标记已访问节点防止重复入队,但注意该实现中同一节点可能以更短时间多次入队(类SPFA特性)
    • 适用于犯罪推理、可达性分析、时间约束下的最短路径类问题

【解题思路】

【算法标签】

普及 #BFS-图

【代码详解】

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

// 定义常量
const int N = 505;  // 最大节点数

// 定义类型别名
typedef pair<int, int> PII;  // pair<节点, 当前时间>

// 变量定义
int g[N][N];  // 邻接矩阵存储图
int ans[N];   // 存储满足条件的奶牛编号
int cur;      // 满足条件的奶牛数量
int pos[N];   // 每头奶牛所在的农场编号
int dist[N];  // 从起点到各点的最短距离
bool vis[N];  // 标记数组,用于BFS
int f, p, c, m;  // f:农场数量, p:道路数量, c:奶牛数量, m:最大时间限制

// 广度优先搜索函数
void bfs()
{
    queue<PII> q;  // 队列存储<节点, 当前累计时间>
    q.push({1, 0});  // 从农场1开始,时间为0

    // 初始化距离数组为无穷大
    memset(dist, 0x3f, sizeof(dist));
    dist[1] = 0;  // 起点到自己的距离为0

    // BFS主循环
    while (!q.empty())
    {
        int u = q.front().first;      // 当前节点
        int time = q.front().second;  // 到达当前节点的累计时间
        q.pop();

        // 如果节点已访问,跳过
        if (vis[u])
        {
            continue;
        }

        // 如果当前时间超过限制,跳过
        if (time > m)
        {
            continue;
        }

        // 标记节点为已访问
        vis[u] = 1;

        // 更新到当前节点的最短距离
        dist[u] = min(dist[u], time);

        // 遍历所有可能的邻居节点
        for (int i = 1; i <= f; i++)
        {
            // 跳过自身和没有直接连接的节点
            if (i != u && g[u][i] != INT_MAX)
            {
                // 将相邻节点加入队列,时间累加边权
                q.push({i, time + g[u][i]});
            }
        }
    }
}

int main()
{
    // 输入基本参数
    cin >> f >> p >> c >> m;

    // 初始化邻接矩阵为无穷大
    for (int i = 1; i <= f; i++)
    {
        for (int j = 1; j <= f; j++)
        {
            g[i][j] = INT_MAX;  // 表示没有直接连接
        }
    }

    // 输入道路信息
    for (int i = 1; i <= p; i++)
    {
        int u, v, w;  // 起点,终点,边权(时间)
        cin >> u >> v >> w;

        // 无向图,取最小边权(处理重边)
        g[u][v] = min(g[u][v], w);
        g[v][u] = min(g[v][u], w);
    }

    // 输入奶牛位置
    for (int i = 1; i <= c; i++)
    {
        cin >> pos[i];
    }

    // 运行BFS计算"可达"路径
    bfs();

    // 检查每头奶牛是否可以在时间m内到达
    for (int i = 1; i <= c; i++)
    {
        // 如果从农场1到奶牛所在农场的"距离"不超过m
        if (dist[pos[i]] <= m)
        {
            ans[++cur] = i;  // 记录奶牛编号
        }
    }

    // 注意:这里的排序被注释掉了
    // sort(ans+1, ans+cur+1);

    // 输出结果
    cout << cur << endl;  // 输出满足条件的奶牛数量

    for (int i = 1; i <= cur; i++)
    {
        cout << ans[i] << endl;  // 输出满足条件的奶牛编号
    }

    return 0;
}

【运行结果】

7 6 5 8
1 4 2
1 2 1
2 3 6
3 5 5
5 4 6
1 7 9
1
4
5
3
7
4
1
2
3
4
posted @ 2026-08-29 09:39  团爸讲算法  阅读(6)  评论(0)    收藏  举报