AcWing342道路與航線(dijkstra+拓撲排序)
題目地址:https://www.acwing.com/problem/content/344/
題目描述:
農夫約翰正在一個新的銷售區域對他的牛奶銷售方案進行調查。
他想把牛奶送到T個城鎮,編號為1~T。
這些城鎮之間通過R條道路 (編號為1到R) 和P條航線 (編號為1到P) 連線。
每條道路i或者航線i連線城鎮Ai到Bi,花費為Ci。
對於道路,0≤Ci≤10,000;然而航線的花費很神奇,花費Ci可能是負數(−10,000≤Ci≤10,000)。
道路是雙向的,可以從Ai到Bi,也可以從Bi到Ai,花費都是Ci。
然而航線與之不同,只可以從Ai到Bi。
事實上,由於最近恐怖主義太囂張,為了社會和諧,出臺了一些政策:保證如果有一條航線可以從
由於約翰的奶牛世界公認十分給力,他需要運送奶牛到每一個城鎮。
他想找到從傳送中心城鎮S把奶牛送到每個城鎮的最便宜的方案。
輸入格式
第一行包含四個整數T,R,P,S。
接下來R行,每行包含三個整數(表示一個道路)Ai,Bi,Ci。
接下來P行,每行包含三個整數(表示一條航線)Ai,Bi,Ci。
輸出格式
第1..T行:第i行輸出從S到達城鎮i的最小花費,如果不存在,則輸出“NO PATH”。
資料範圍
1≤T≤25000
1≤R,P≤50000,
1≤Ai,Bi,S≤T,
題解:這道題中,對於道路而言是雙向的,航線是單向的。並且,規定,存在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