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。
事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台了一些政策:保证如果有一条航线可以从Ai到Bi,那么保证不可能通过一些道路和航线从Bi回到Ai。
由于约翰的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。
他想找到从发送中心城镇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
作者:孙建钊
出处:http://www.cnblogs.com/sunjianzhao/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。