每周一算法:最短路计数

本文介绍了一种算法,用于解决给定无向无权图中,从顶点1出发到所有其他顶点的最短路有多少条不同方案的问题,利用了动态规划和BFS/Dijkstra算法的思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

给出一个NNN个顶点 MMM 条边的无向无权图,顶点编号为 111NNN

问从顶点 111 开始,到其他每个点的最短路有几条。

输入格式

第一行包含 222 个正整数 N,MN,MN,M,为图的顶点数与边数。

接下来 MMM 行,每行两个正整数 x,yx,yx,y,表示有一条顶点 xxx 连向顶点 yyy 的边,请注意可能有自环与重边。

输出格式

输出 NNN 行,每行一个非负整数,第 iii 行输出从顶点 111 到顶点 iii 有多少条不同的最短路,由于答案有可能会很大,你只需要输出对 100003100003100003 取模后的结果即可。

如果无法到达顶点 iii 则输出 000

样例 #1

样例输入 #1

5 7
1 2
1 3
2 4
3 4
2 3
4 5
4 5

样例输出 #1

1
1
1
2
4

提示

【数据范围】
1≤N≤1051≤N≤10^51N105,
1≤M≤2×1051≤M≤2×10^51M2×105

算法思想

根据题目描述,求从起点 111 到每个顶点有多少条不同的最短路,即求最短路的方案数。可以使用动态规划的思想,定义状态f[i]f[i]f[i]表示起点到顶点iii的最短路的方案数。

要在图中计算状态,需要先构造出图的拓扑序列。但是题目中给出的是无向无权图,通过测试样例可以看出,图中有可能存在环,如下图所示,不一定存在拓扑序列,因此不能通过循环迭代直接计算状态。
在这里插入图片描述
由于求的是最短路的方案数,考虑能否使用最短路算法,在计算最短路的同时将状态计算出来。要使用最短路计算方案数需要要满足不能存在代价为000的环,否则经过该环的最短路的方案数为无穷大。而题目中题目给出的是无向无权图,可以认为边权都为111,因此不存在代价为000的环。

那么有哪些最短路算法可以进行状态计算呢?

题目求的是起点到其余顶点的最短路,那么可以选择的最短路算法有:BFS、Dijkstra、Bellman-Ford(SPFA)等,是不是这些算法都可以用来计算状态呢?要计算iii点的状态,必须保证iii阶段所依赖的状态都已经被计算出来,也就是说在计算最短路时,能够找到一个拓扑序来计算状态。这样就需要最短路算法能够构造出一个最短路拓扑图。如下图所示:
在这里插入图片描述

而对于下列算法:

  • BFS算法计算(边权相同的)最短路时,是一层一层进行扩展的,每个点只会入队111次,出队111次,进队和出队都是满足拓扑序的。
  • Dijkstra算法计算最短路时,每个点只会出队111次,当一个点出队时,是不会更新前面已经出队的点到起点的最短路,因此出队序列也满足拓扑序。
  • Bellman-Ford(SPFA)算法计算最短路时,每个点可以入队出队多次,当一个点出队时可能会更新之前出队的点的最短路,这样就不能保证出队序列具备拓扑序。

也就是说, BFS和 Dijkstra算法在求最短路过程中可以进行状态计算。

算法流程

  • 将起点111的最短路径方案数初始化为111,即f[1]=1f[1] = 1f[1]=1
  • 在求解最短路的过程中
    • vvv点到起点的最短路能够被uuu点松弛,即dis[v]>dis[u]+1dis[v] > dis[u] + 1dis[v]>dis[u]+1,则到vvv的最短路方案数等于到uuu点的最短路方案数,即f[v]=f[u]f[v] = f[u]f[v]=f[u]
    • vvv点到起点的最短路等于经过uuu点中转的距离,则需要累加到uuu点的最短路方案数,即f[v]=f[v]+f[u]f[v] =f[v] + f[u]f[v]=f[v]+f[u]

代码实现

#include <bits/stdc++.h>
//注意:边数为200000,无向图需要建双向边,所以边的总数为400010
const int N = 100010, M = 400010, mod = 100003;
int n, m;
int h[N], e[M], ne[M], idx;
//dist[i]表示从1号点到i号点的最短距离
//f[i]表示从1到点到i号点的最短距离的路径数
int dis[N], f[N], q[N];
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void bfs()
{
    memset(dis, 0x3f, sizeof dis);
    //注意,到1号点的最短路径数初始为1
    dis[1] = 0, f[1] = 1;
    //bfs每个点只会入队一次,使用普通队列即可
    int hh = 0, tt = 0;
    q[0] = 1;
    while(hh <= tt)
    {
        int u = q[hh ++];
        for(int i = h[u]; ~i; i = ne[i])
        {
            int v = e[i];
            //可以更新最短距离
            if(dis[v] > dis[u] + 1)
            {
                dis[v] = dis[u] + 1;
                q[++ tt] = v;
                //此时的路径数不变
                f[v] = f[u];
            }   
            else if(dis[v] == dis[u] + 1)//如果最短距离相等,累加最短路径数
            {
                f[v] = (f[v] + f[u]) % mod;
            }
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    while(m --)
    {
        int a, b;
        scanf("%d%d", &a, &b);
       
        add(a, b), add(b, a);  //无向图,双向边
    }
    bfs();
    for(int i = 1; i <= n; i++) printf("%d\n", f[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少儿编程乔老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值