蓝桥杯 算法提高 道路和航路 Djkstra + 拓扑序

一、内容

问题描述

农夫约翰正在针对一个新区域的牛奶配送合同进行研究。他打算分发牛奶到T个城镇(标号为1..T),这些城镇通过R条标号为(1..R)的道路和P条标号为(1..P)的航路相连。

每一条公路i或者航路i表示成连接城镇Ai(1<=A_i<=T)和Bi(1<=Bi<=T)代价为Ci。每一条公路,Ci的范围为0<=Ci<=10,000;由于奇怪的运营策略,每一条航路的Ci可能为负的,也就是-10,000<=Ci<=10,000。

每一条公路都是双向的,正向和反向的花费是一样的,都是非负的。

每一条航路都根据输入的Ai和Bi进行从Ai->Bi的单向通行。实际上,如果现在有一条航路是从Ai到Bi的话,那么意味着肯定没有通行方案从Bi回到Ai。

农夫约翰想把他那优良的牛奶从配送中心送到各个城镇,当然希望代价越小越好,你可以帮助他嘛?配送中心位于城镇S中(1<=S<=T)。
输入格式

输入的第一行包含四个用空格隔开的整数T,R,P,S。

接下来R行,描述公路信息,每行包含三个整数,分别表示Ai,Bi和Ci。

接下来P行,描述航路信息,每行包含三个整数,分别表示Ai,Bi和Ci。
输出格式
输出T行,分别表示从城镇S到每个城市的最小花费,如果到不了的话输出NO PATH。

样例输入

6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10

样例输出

NO PATH
NO PATH
5
0
-95
-100

数据规模与约定

对于20%的数据,T<=100,R<=500,P<=500;

对于30%的数据,R<=1000,R<=10000,P<=3000;

对于100%的数据,1<=T<=25000,1<=R<=50000,1<=P<=50000。

二、思路

  • 由于道路是双向的(非负),而航线是单向(可能为负,但是航线没有回路)。 那么我们可以按照道路相连的点划分为几个连通块 。航线就是连接各个连通块的边,根据题目描述,这些连通块符合拓扑序。
  • 由于每个连通块内部的点都是道路相连(道路非负),那么我们可以在连通块内部求一次djkstra。按照拓扑序的顺序对每个连通块求解一次djkstra。按照拓扑序的顺序对每个连通块求最短路,这样就可以在求某个连通块时,其他连通块若能到达这个块,那么必然起点到其他点的最短路已经求好了,符合航线不构成回路的设定。

三、代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
#include <iostream>
using namespace std;
const int N = 25005, M = 150005, INF = 0x3f3f3f3f;
struct E {
	int v, next, w;
} e[M];
struct Node {
	int dis, v;
	Node(int d, int v):dis(d), v(v){}
	bool operator < (const Node & w) const {
		return dis > w.dis;
	} 
};
int n, mr, mp, s, u, v, w, len = 1, head[N], d[N], id[N], in[N], cnt; 
//id记录这个点属于哪个连通块 cnt记录连通块的个数  in记录连通块的入度 
vector<int> b[N]; //记录某个连通块里面的所有点 
queue<int> q;
bool vis[N];
void add(int u, int v, int w) {
	e[len].v = v;
	e[len].w = w;
	e[len].next = head[u];
	head[u] = len++;
}
void dfs(int u, int bid) {
	id[u] = bid;
	b[bid].push_back(u); //将这个点放入这个连通块
	for (int j = head[u]; j; j = e[j].next) {
		int v = e[j].v;
		if (!id[v]) dfs(v, bid); //代表还没访问过 那么dfs一下 
	} 
}
void djkstra(int bid) {
	priority_queue<Node> pq;
	for (int i = 0; i < b[bid].size(); i++) {
		//将这个连通块内部的所有点入优先队列(因为不知道从哪里作为起点)
		int v = b[bid][i];
		pq.push(Node(d[v], v)); 
	}
	while (!pq.empty()) {
		int u = pq.top().v;
		pq.pop();
		if (vis[u]) continue;
		vis[u] = true;
		for (int j = head[u]; j; j = e[j].next) {
			int v = e[j].v;
			int w = e[j].w;
			if (d[v] > d[u] + w) {
				d[v] = d[u] + w;
				if (id[v] == id[u]) pq.push(Node(d[v], v)); //如果在這個连通块里面才入队 因为我们求的是在这个连通块里面的最短路
			}
			//注意q中保存的是连通块的id 
			if (id[v] != id[u] && (--in[id[v]]) == 0) q.push(id[v]); //可能连接连通块的其他边 那么就入度减少 
		} 
	} 
} 
void topsort() {
	memset(d, 0x3f, sizeof(d));
	d[s] = 0; //起点设置为0 
	//将入度为0的加入队列 
	for (int i = 1; i <= cnt; i++) if(!in[i]) q.push(i);
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		djkstra(u); //每个连通块内部求解一次最短路 
	} 
}
int main() {
	scanf("%d%d%d%d", &n, &mr, &mp, &s);
	for (int i = 1; i <= mr; i++) {
		scanf("%d%d%d", &u, &v, &w);
		add(u, v, w); 
		add(v, u, w); 
	}
	//求出连通块的个数
	for (int i = 1; i <= n; i++) {
		if (!id[i]) dfs(i, ++cnt);
	} 
	//输入航线
	for (int i = 1; i <= mp; i++) {
		scanf("%d%d%d", &u, &v, &w);
		add(u, v, w);
		in[id[v]]++;//v所在的连通块的入度++ 
	} 
	topsort(); //进行拓扑序 求解每个连通块内部的最短路
	for (int i = 1; i <= n; i++) {
		if (d[i] > INF / 2) printf("NO PATH\n");
		else printf("%d\n", d[i]);
	} 
	return 0;
}
发布了358 篇原创文章 · 获赞 289 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_41280600/article/details/104109781