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/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文地址:https://www.cnblogs.com/sunjianzhao/p/13621825.html