题解:洛谷 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
【核心思想】
-
问题分析:给定 \(F\) 个农场(草地)和 \(P\) 条双向道路(通行时间 \(1\) 到 \(7 \times 10^4\) 秒),以及 \(C\) 头奶牛的位置。谷仓在农场 \(1\),卫星照片拍摄于盗窃前 \(M\) 秒。求哪些奶牛能在 \(M\) 秒内从当前位置到达农场 \(1\)。本质是一个单源最短路径 + 阈值筛选问题。
-
算法选择:
- BFS(适用于小权值)或 Dijkstra(通用):由于边权范围较大(\(1\) 到 \(7 \times 10^4\)),但农场数 \(F \leq 500\) 较小,代码中使用的是类BFS的队列搜索(实际为SPFA思想的简化版),通过邻接矩阵存储并处理重边
-
关键步骤:
- 建图:用邻接矩阵 \(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\) 为嫌疑犯
- 输出结果:先输出嫌疑犯数量,再按编号顺序输出(注意代码中排序被注释,但输入顺序恰好有序)
-
时间/空间复杂度:
- 时间复杂度:\(O(F^2 + P + C)\),BFS在邻接矩阵上为 \(O(F^2)\),筛选 \(O(C)\)
- 空间复杂度:\(O(F^2)\),邻接矩阵存储
-
单源最短路 + 阈值筛选的核心思想:
- 逆向思维:不计算每头奶牛到农场 \(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
浙公网安备 33010602011771号