1. 程式人生 > 實用技巧 >AcWing342道路與航線(dijkstra+拓撲排序)

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\n";//至於這裡選擇INF/2其實就是一個嘗試的概念,如果資料比較厲害的話,其實會過不了 
        else cout<<dis[i]<<endl;
    }
    return 0;
}

寫於:2020/9/6 15:13