每周一算法:最短路计数

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

题目描述

给出一个 N N N个顶点 M M M 条边的无向无权图,顶点编号为 1 1 1 N N N

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

输入格式

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

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

输出格式

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

如果无法到达顶点 i i i 则输出 0 0 0

样例 #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 ≤ 1 0 5 1≤N≤10^5 1N105,
1 ≤ M ≤ 2 × 1 0 5 1≤M≤2×10^5 1M2×105

算法思想

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

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

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

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

而对于下列算法:

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

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

算法流程

  • 将起点 1 1 1的最短路径方案数初始化为 1 1 1,即 f [ 1 ] = 1 f[1] = 1 f[1]=1
  • 在求解最短路的过程中
    • v v v点到起点的最短路能够被 u u u点松弛,即 d i s [ v ] > d i s [ u ] + 1 dis[v] > dis[u] + 1 dis[v]>dis[u]+1,则到 v v v的最短路方案数等于到 u u u点的最短路方案数,即 f [ v ] = f [ u ] f[v] = f[u] f[v]=f[u]
    • v v v点到起点的最短路等于经过 u u u点中转的距离,则需要累加到 u u u点的最短路方案数,即 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、付费专栏及课程。

余额充值