• AcWing342道路与航线(dijkstra+拓扑排序)


    题目地址https://www.acwing.com/problem/content/344/

    题目描述

    农夫约翰正在一个新的销售区域对他的牛奶销售方案进行调查。

    他想把牛奶送到T个城镇,编号为1~T。

    这些城镇之间通过R条道路 (编号为1到R) 和P条航线 (编号为1到P) 连接。

    每条道路 i或者航线 i 连接城镇AiBi,花费为Ci

    对于道路,0Ci10,000;然而航线的花费很神奇,花费Ci可能是负数(10,000Ci10,000)。

    道路是双向的,可以从AiBi,也可以从Bi到Ai,花费都是Ci。

    然而航线与之不同,只可以从Ai到Bi。

    事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台了一些政策:保证如果有一条航线可以从Ai到Bi,那么保证不可能通过一些道路和航线从Bi回到Ai。

    由于约翰的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。

    他想找到从发送中心城镇S把奶牛送到每个城镇的最便宜的方案。

    输入格式

    第一行包含四个整数T,R,P,S。

    接下来R行,每行包含三个整数(表示一个道路)Ai,Bi,Ci

    接下来P行,每行包含三个整数(表示一条航线)Ai,Bi,Ci

    输出格式

    第1..T行:第i行输出从S到达城镇i的最小花费,如果不存在,则输出“NO PATH”。

    数据范围

    1T25000
    1R,P50000,
    1Ai,Bi,ST,

    题解:这道题中,对于道路而言是双向的,航线是单向的。并且,规定,存在A到B的航线,则不可能会从B回到A.那么这道题的图就是一个个由道路组成的联通块,将这些联通块看作一个结点时,那么航线和联通块就组成了一个有向无环图。在联通块内部不存在航线。我们需要求解的是s到所有点的最短距离。

    1、先输入所有双向道路,然后dfs求出所有联通块,计算两个数组:id[]存储每个点属于哪个联通块;vector<int>block[]存储每个联通块有哪些结点
    2、输入所有航线,同时统计出每个联通快的入度。
    3、按照拓扑序依次处理每个联通块
    4、每次从队头取出一个联通块的编号bid
    5、将该block[bid]中的所有点加入堆中,然后对堆中所有点跑dijkstra算法
    6、每次取出堆中距离最小的点ver
    7、然后遍历ver的所有邻点j.如果id[ver]==id[j],那么如果j能够被更新,则将j插入堆中;
         如果id[ver]!=id[j],则将id[j]这个联通块的入度减1,如果减成0,则将其插入拓扑排序的队列中

    AC代码

    #include <iostream>
    #include <cstdio>
    #include <vector>
    #include <queue>
    #include <cstring>
    
    using namespace std;
    
    const int N=25010,M=150010;
    typedef pair<int,int> PII;
    #define INF 0x3f3f3f3f
    //链式前向星 
    struct node{
        int from,to,dis,next;
    }edge[M];
    int head[N],dis[N],cnt=0;
    
    int id[N]={0},bcnt=0,din[N]={0};//id[]存储结点属于哪一个联通块;din[]存储联通块的入度;bcnt联通块编号的标记变量 
    int n,mr,mp,s;
    vector<int>block[N];//存储每个联通块所包含的结点 
    queue<int>q;//拓扑排序的队列 
    
    void addedge(int u,int v,int w){//链式前向星的加边操作 
        cnt++;
        edge[cnt].from=u;
        edge[cnt].to=v;
        edge[cnt].dis=w;
        edge[cnt].next=head[u];
        head[u]=cnt;
    }
    
    void dfs(int u,int bid){//求出bid编号的联通块的所有结点 
        id[u]=bid;
        for(int i=head[u];~i;i=edge[i].next){
            if(!id[edge[i].to] ) dfs(edge[i].to,bid);
        }
    }
    
    void dijkstra(int x){
        int st[N]={0};
        priority_queue<PII,vector<PII>,greater<PII> >heap;
        for(int i=0,u;i<block[x].size();i++){
            u=block[x][i];
            heap.push(make_pair(dis[u],u)); 
            //一次将该联通块中所有的结点都加入到dijkstra中的堆heap中。注意这个很重要,一定要深刻理解 
        }
        while(!heap.empty()){
            PII now=heap.top();heap.pop();
            int u=now.second,w=now.first;
            if(st[u]) continue;
            st[u]=1;
            for(int i=head[u];~i;i=edge[i].next){
                int v=edge[i].to;
                if(dis[v]>dis[u]+edge[i].dis){
                    dis[v]=dis[u]+edge[i].dis;//单纯更新的不需要满足一定是在同一个联通块中,因为其他联通块可能会被通过航线进行更新
                    //但是不要将其加入dijkstra的队列中,因为会在遍历其所在的联通块是=时再更新其邻居节点 
                    if(id[u]==id[v]) heap.push(make_pair(dis[v],v)); //只有是在同一个联通块中才需要放入dijkstra的队列中 
                }
                if(id[u]!=id[v]&&(--din[id[v]])==0) q.push((id[v]));
            }
        }
    }
    
    void topsort(){
        memset(dis,0x3f,sizeof(dis));
        dis[s]=0;
        for(int i=1;i<=bcnt;i++) 
            if(din[i]==0) q.push(i);
        while(!q.empty()){
            int x=q.front();q.pop();
            dijkstra(x);
        }
    }
    
    int main(){
        scanf("%d%d%d%d",&n,&mr,&mp,&s);
        memset(head,-1,sizeof(head));
        for(int i=1,u,v,w;i<=mr;i++){
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
            addedge(v,u,w);
        }
        for(int i=1;i<=n;i++){//求出每个结点所属于哪一个联通块 
            if(!id[i])
                dfs(i,++bcnt);
        }
        for(int i=1;i<=n;i++) block[id[i]].push_back(i);//求出每个连通块有哪些结点 
        
        for(int i=1,u,v,w;i<=mp;i++){
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
            din[id[v]]++;//对航线计算入度 
        }
        topsort();//进行拓扑排序 
        for(int i=1;i<=n;i++){
            //注意这里dis[]并不是等于INF才NO PATH,当s到达不了的联通块,但是哪些到达不了的联通块之间的航线可能时负的,所以dis就会小于INF 
            if(dis[i]>INF/2) cout<<"NO PATH
    ";//至于这里选择INF/2其实就是一个尝试的概念,如果数据比较厉害的话,其实会过不了 
            else cout<<dis[i]<<endl;
        }
        return 0;
    }

    写于:2020/9/6 15:13


    作者:孙建钊
    出处:http://www.cnblogs.com/sunjianzhao/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    C++之流与文件
    C++之检测文件结尾
    统信UOS
    统信UOS
    统信UOS
    八、【Docker笔记】使用Dockerfile创建镜像
    七、【Docker笔记】Docker中网络基础配置
    六、【Docker笔记】Docker数据管理
    五、【Docker笔记】Dockers仓库
    四、【Docker笔记】Docker容器
  • 原文地址:https://www.cnblogs.com/sunjianzhao/p/13621825.html
Copyright © 2020-2023  润新知