题解:洛谷 P1144 最短路计数

【题目来源】

洛谷: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. 问题分析:给定无向无权图,求从顶点 \(1\) 到每个顶点的最短路径数量(对 \(100003\) 取模)。这是一个BFS 最短路计数问题,关键在于利用无权图 BFS 按层扩展的特性,在第一次访问节点时记录路径数,在再次以相同距离访问时累加路径数。

  2. 算法选择

    • BFS(广度优先搜索):无权图最短路的标准算法,按距离层次扩展
    • 路径数动态维护cnt[v] 记录到 \(v\) 的最短路径数,首次访问时继承,同层访问时累加
  3. 关键步骤

    • 初始化dist[1] = 0, cnt[1] = 1,其余 `dist = -1, cnt = 0$
    • BFS 扩展(队列非空时):
      • 取出队首 \(u\)
      • 遍历邻接点 \(v\)
        • 首次访问dist[v] == -1):dist[v] = dist[u] + 1cnt[v] = cnt[u]\(v\) 入队
        • 同层到达dist[v] == dist[u] + 1):cnt[v] = (cnt[v] + cnt[u]) % mod
    • 输出 cnt[1..n]
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N + M)\),每个节点和边最多被访问一次
    • 空间复杂度:\(O(N + M)\),邻接表、距离数组、计数数组
  5. BFS 最短路计数的核心思想

    • 层次分离性:BFS 按距离层次扩展,第一次访问 \(v\) 时得到的 dist[v] 一定是最短距离。后续只有距离相同的边才能贡献额外最短路
    • 路径数传递:若 \(u\)\(1\)\(cnt[u]\) 条最短路,且 \(v\) 首次通过 \(u\) 到达,则 \(v\) 继承 \(cnt[u]\)
    • 同层累加:若多个父节点 \(u_1, u_2, \ldots\) 都在最短路的上一层且都指向 \(v\),则 \(v\) 的最短路数为各父节点路径数之和
    • 取模处理:路径数可能极大,每次累加后取模防止溢出
    • 适用于"无权图单源最短路计数"问题,核心在于 BFS 的层次性和路径数的动态传递

【算法标签】

普及+ #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
posted @ 2026-08-10 12:25  团爸讲算法  阅读(23)  评论(0)    收藏  举报