zoukankan      html  css  js  c++  java
  • 最短路计数

    链接

    https://www.acwing.com/problem/content/description/1136/

    题目

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

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

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

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

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

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

    数据范围
    (1≤N≤10^5,)
    (1≤M≤2×10^5)
    输入样例:

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

    输出样例:

    1
    1
    1
    2
    4
    

    思路

    先抛开这道题想一下最短路计数问题如何解决:
    引入一个概念——“最短路树”:
    假设每个点的父亲节点是这个点任意一条最短路径的前节点,那么就会形成一颗树,同时也会有其他条最短路的前节点,总之也会满足拓扑序。

    求解最短路算法有:
    bfs:只适用于边权为0和1的图,对于每个点更新最短路时可以直接更新最短路数量,因为每个点只会入队和出队一次,bfs的过程满足拓扑序。
    Dijkstra:也在每个点更新最短路时可以直接更新最短路数量,因为每个点只会作为距离最近的节点出队一次。
    spfa:不能直接计算最短路数量,因为每个节点都有可能多次入队和出队,spfa本质是对于以边相同的点为同一层优先搜索的,举个例子:

    假设spfa先搜完1->2->3->4,3和4的最短路都确定,最短路条数都为1,再搜到1->6->7->8->3这条路时,只会将3的最短路计数加1,4的最短路还是1。
    那么就会有人提出,当最短路数量更新时也加入队列,那么如果将3加入队列,对于4,不能确定最短路条数时加1还是加2。
    但是这并不代表spfa不能求最短路条数。当边权有负时,只能用spfa计数,一种有效的做法是:先用spfa将每个点的单源最短路求出,枚举每条边,当有(d[v[i]]=w[i]+d[u[i]])时,建一条边u到v的边,最后做拓扑DP,记录每个点可以被走到的方案数。(写这么多其实也是向讲一下spfa的做法,但是现在还没找到有这样的题,如果有机会给学弟们出题自己可以出一道)
    对于这道题边权为1的图,可以直接用bfs,也可以用spfa(边权为1的spfa对于每个距离相同点可以保证同时搜到)。对于每个点v,u可以更新v的最短距离,那么v的计数等于u的计数;如果u的更新等于d[v],那么v的计数加上u的计数。

    代码

    #include<bits/stdc++.h>
    using namespace std;
    const int N=100010,M=400010,mod=100003;
    int h[N],nex[M],e[M],idx;
    int d[N],q[N],st[N],cnt[N],n,m;
    void add(int u,int v){
        nex[idx]=h[u];
        e[idx]=v;
        h[u]=idx++;
    }
    void bfs(){
        memset(d,0x3f,sizeof d);
        d[1]=0;
        cnt[1]=1;
        int hh=0,tt=0;
        q[tt++]=1;
        st[1]=1;
        while(hh!=tt){
            int u=q[hh++];
            if(hh==N)  hh=0;
            st[u]=0;
            for(int i=h[u];~i;i=nex[i]){
                int v=e[i];
                if(d[v]>d[u]+1){
                    d[v]=(d[u]+1)%mod;
                    cnt[v]=cnt[u];
                    if(!st[v]) {
                        q[tt++]=v;
                        if(tt==N) tt=1;
                        st[v]=1;
                    }
                }
                else if(d[v]==d[u]+1){
                    (cnt[v]+=cnt[u])%=mod;
                    if(!st[v]) {
                        q[tt++]=v;
                        if(tt==N) tt=1;
                        st[v]=1;
                    }
                }
            }
        }
    }
    int main(){
        memset(h,-1,sizeof h);
        cin>>n>>m;
        for(int i=1;i<=m;++i){
            int u,v;
            cin>>u>>v;
            add(u,v);
            add(v,u);
        }
        bfs();
        for(int i=1;i<=n;++i){
            cout<<cnt[i]<<endl;
        }
        return 0;
    }
    
  • 相关阅读:
    LINUX查看进程开始时间、结束时间、运行时间
    excel字符处理函数
    oracle RMAN参数配置详解
    Linux添加双IP
    免费软电话 — XLite 的安装及配置向导
    Asterisk实现寻呼对讲广播的Page()命令详解
    自动化工具的重要性
    负载均衡之应用请求路由模块的使用(ARR)(七)[使用ARR来实现主机名共享]
    负载均衡之应用请求路由模块的使用(ARR)(二)[安装]
    需求管理随笔
  • 原文地址:https://www.cnblogs.com/jjl0229/p/12755656.html
Copyright © 2011-2022 走看看