道路与航线 (无向图缩点 拓扑排序求最短路

342. 道路与航线
思路:
看起来像是道最短路模板题,但是 s p f a spfa spfa T T T 掉,优化的 s p f a spfa spfa 好像可以过掉这个题(ACwing时限大了
不过我们这里不讨论优化的 s p f a spfa spfa
题目限制比较奇妙,双向边的权值是非负的,单向边的权值可能为负,但是单向边不会构成环
如果我们把所有由双向边构成的连通块看成一个点,那么之后添加完单向边就构成了一个有向无环图
对于 D A G DAG DAG,我们可以用拓扑排序很快的求出单源最短路
而对于每个连通块,我们可以用 D i j k s t r a Dijkstra Dijkstra 解决

看完题解感觉好像懂了,首先 d f s dfs dfs 对连通块染色,然后对连通块建有向图跑拓扑排序求最短路
这些听起来都很合理
然后就是对于每个连通块执行 D i j k s t r a Dijkstra Dijkstra 算法,具体步骤可以看代码
它把所有点都先 p u s h push push 进来的行为我不是非常能理解
然后它的拓扑排序更新是在执行 D i j Dij Dij 的过程中顺便进行的
我大受震撼,不过确实有点道理
这个过程真的好抽象
样例的图大概是这样的
在这里插入图片描述

code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 3e4 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to, w;
}e[maxn * 10];
int head[maxn], cnt;
int color[maxn], dcnt;
int in[maxn];
bool vis[maxn];
ll dis[maxn];
vector <int> d[maxn];
queue <int> q1;

void add(int x, int y, int z){
	e[++cnt].to = y;
	e[cnt].w = z;
	e[cnt].next = head[x];
	head[x] = cnt;
}
void dfs(int x){
	color[x] = dcnt;
	d[dcnt].push_back(x);
	for(int i = head[x]; i; i = e[i].next){
		int to = e[i].to;
		if(color[to]) continue;
		dfs(to);
	}
}
void dij(int s){
	priority_queue <pair<ll,int>,vector<pair<ll,int> >, greater<pair<ll,int> > > q;
	for(int x : d[s]) q.push({dis[x], x});
	while(!q.empty()){
		int x = q.top().second;q.pop();
		if(vis[x]) continue;
		vis[x] = 1;
		for(int i = head[x]; i; i = e[i].next){
			int to = e[i].to;
			if(dis[to] > dis[x] + e[i].w){
				dis[to] = dis[x] + e[i].w;
				if(color[to] == s) q.push({dis[to], to});
			}
			if(color[to] != s) {
				in[color[to]]--;
				if(!in[color[to]]) q1.push(color[to]);
			}
		}
	}
}
void topsort(int s){
	mem(dis, 0x3f);dis[s] = 0;// 保证所有的dis[x]都是由s转移来的,由其他地方转移来的dis会非常大,可以很好的区分
	for(int i = 1; i <= dcnt; ++i) if(!in[i]) q1.push(i);
	while(!q1.empty()){
		dij(q1.front());q1.pop();
	}
}
void work()
{
	int r, p, s;cin >> n >> r >> p >> s;
	for(int i = 1; i <= r; ++i){
		int x, y, z;cin >> x >> y >> z;add(x, y, z);add(y, x, z);
	} 
	for(int i = 1; i <= n; ++i) if(!color[i]) ++dcnt, dfs(i);
	for(int i = 1; i <= p; ++i){
		int x, y, z;cin >> x >> y >> z;add(x, y, z);in[color[y]]++;
	}
	topsort(s);
	for(int i = 1; i <= n; ++i){
		if(dis[i] > INF / 2) cout << "NO PATH\n";// 不是由起点s转移的
		else cout << dis[i] << endl;
	}
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值