题解:洛谷 P1144 最短路计数
【题目来源】
【题目描述】
给出一个 \(N\) 个顶点 \(M\) 条边的无向无权图,顶点编号为 \(1\sim N\)。问从顶点 \(1\) 开始,到其他每个点的最短路有几条。
【输入】
第一行包含 \(2\) 个正整数 \(N,M\),为图的顶点数与边数。
接下来 \(M\) 行,每行 \(2\) 个正整数 \(x,y\),表示有一条连接顶点 \(x\) 和顶点 \(y\) 的边,请注意可能有自环与重边。
【输出】
共 \(N\) 行,每行一个非负整数,第 \(i\) 行输出从顶点 \(1\) 到顶点 \(i\) 有多少条不同的最短路,由于答案有可能会很大,你只需要输出 $ ans \bmod 100003$ 后的结果即可。如果无法到达顶点 \(i\) 则输出 \(0\)。
【输入样例】
5 7
1 2
1 3
2 4
3 4
2 3
4 5
4 5
【输出样例】
1
1
1
2
4
【核心思想】
-
问题分析:给定无向无权图,求从顶点 \(1\) 到每个顶点的最短路径数量(对 \(100003\) 取模)。这是一个BFS 最短路计数问题,关键在于利用无权图 BFS 按层扩展的特性,在第一次访问节点时记录路径数,在再次以相同距离访问时累加路径数。
-
算法选择:
- BFS(广度优先搜索):无权图最短路的标准算法,按距离层次扩展
- 路径数动态维护:
cnt[v]记录到 \(v\) 的最短路径数,首次访问时继承,同层访问时累加
-
关键步骤:
- 初始化:
dist[1] = 0, cnt[1] = 1,其余 `dist = -1, cnt = 0$ - BFS 扩展(队列非空时):
- 取出队首 \(u\)
- 遍历邻接点 \(v\):
- 首次访问(
dist[v] == -1):dist[v] = dist[u] + 1,cnt[v] = cnt[u],\(v\) 入队 - 同层到达(
dist[v] == dist[u] + 1):cnt[v] = (cnt[v] + cnt[u]) % mod
- 首次访问(
- 输出
cnt[1..n]
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(N + M)\),每个节点和边最多被访问一次
- 空间复杂度:\(O(N + M)\),邻接表、距离数组、计数数组
-
BFS 最短路计数的核心思想:
- 层次分离性:BFS 按距离层次扩展,第一次访问 \(v\) 时得到的
dist[v]一定是最短距离。后续只有距离相同的边才能贡献额外最短路 - 路径数传递:若 \(u\) 到 \(1\) 有 \(cnt[u]\) 条最短路,且 \(v\) 首次通过 \(u\) 到达,则 \(v\) 继承 \(cnt[u]\) 条
- 同层累加:若多个父节点 \(u_1, u_2, \ldots\) 都在最短路的上一层且都指向 \(v\),则 \(v\) 的最短路数为各父节点路径数之和
- 取模处理:路径数可能极大,每次累加后取模防止溢出
- 适用于"无权图单源最短路计数"问题,核心在于 BFS 的层次性和路径数的动态传递
- 层次分离性:BFS 按距离层次扩展,第一次访问 \(v\) 时得到的
【算法标签】
普及+ #BFS-图
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 1000005, M = 2000005 * 2, mod = 100003;
int n, m; // n: 节点数,m: 边数
int h[N], e[M], ne[M], idx; // 邻接表存储图
int dist[N], cnt[N]; // dist: 节点到起点的最短距离,cnt: 到节点的最短路径数量
vector<int> ans[N]; // 未使用的数组
// 添加无向边
void add(int a, int b)
{
e[idx] = b; // 边的终点
ne[idx] = h[a]; // 指向a的下一条边
h[a] = idx++; // 更新a的头节点
}
queue<int> q; // BFS队列
// 广度优先搜索,计算最短路径长度和数量
void bfs()
{
memset(dist, -1, sizeof(dist)); // 初始化距离为-1(表示未访问)
q.push(1); // 从节点1开始
dist[1] = 0; // 起点距离为0
cnt[1] = 1; // 到起点的最短路径数量为1
while (!q.empty()) // BFS遍历
{
int u = q.front(); // 取出队首
q.pop(); // 弹出队首
for (int i = h[u]; i != -1; i = ne[i]) // 遍历u的所有邻接点
{
int v = e[i]; // 邻接点v
if (dist[v] == -1) // 如果v未访问
{
dist[v] = dist[u] + 1; // 更新v的距离
cnt[v] = cnt[u]; // 路径数量等于u的路径数量
q.push(v); // v入队
}
else if (dist[v] == dist[u] + 1) // 如果找到另一条最短路径
{
cnt[v] = (cnt[v] + cnt[u]) % mod; // 累加路径数量
}
}
}
}
int main()
{
scanf("%d%d", &n, &m); // 输入节点数和边数
memset(h, -1, sizeof(h)); // 初始化邻接表
while (m--) // 输入所有边
{
int u, v;
scanf("%d%d", &u, &v);
add(u, v), add(v, u); // 添加无向边
}
bfs(); // 执行BFS计算最短路径
for (int i = 1; i <= n; i++) // 输出每个节点的最短路径数量
printf("%d\n", cnt[i]);
return 0;
}
【运行结果】
5 7
1 2
1 3
2 4
3 4
2 3
4 5
4 5
1
1
1
2
4
浙公网安备 33010602011771号