AcWing342 求连通块 + 拓扑排序 + dij求最短路

题目链接

思路

虽然有负边,不能直接求最短路,但是观察到边是分为两种的,一种是无向、非负边,另一种则是有向边,可能为负。单单是这样,和只有有向边的情况也差不多,更重要的是另一个条件,若x到y有一条有向边,则保证y到x没有路径。这就意味着只看有向边,我们得到的是一个有向无环图,用拓扑排序就可以得到它们的最短路。但是问题是我们还有无向边,怎么处理?利用无向边将点分为一个个连通块,这样我们就可以把每一个块看做是一个点。块内的点是能够相互到达的,所以有向边一定是从一个块到另一个块的,于是我们可以对块进行拓扑排序。

WA点

当x到y是一条权值为z的有向边的时候,不能够仅仅以min(dis[y], dis[x] + z)来更新dis[y],而是先判断dis[x] != INF时,才执行上述更新。否则,若dis[x] == INF,z < 0,dis[y]的值就会变小,在判断dis[y] == INF而输出NO PATH时,就会有错误。或者不改变,而更改NO PATH的判断条件也是可以的。

代码

#include <bits/stdc++.h>
using namespace std;

const int T = 3e4, M = 5e5 + 10;
typedef long long ll;
const ll INF = 0x3f3f3f3f3f3f3f3f;

int head[T], Next[M], ver[M];
ll edge[M];
ll dis[T];
int tot = 1;
int t, r, p, s;

vector<int> co[T];
int c[T];

void add(int a, int b, ll c) {
	edge[++tot] = c;
	ver[tot] = b;
	Next[tot] = head[a];
	head[a] = tot;
}

int totc = 0;

namespace divi {
	bool vis[T];

	void dfs(int x) {
		vis[x] = true;
		co[totc].push_back(x);
		c[x] = totc;
		for (int i = head[x]; i; i = Next[i]) {
			int y = ver[i];
			if (vis[y]) continue;
			dfs(y);
		}
	}

	void divi_c() {
		memset(vis, false, sizeof(vis));
		for (int i = 1; i <= t; i++) {
			if (vis[i]) continue;
			++totc;
			dfs(i);
		}
	}
}

int indeg[T];
queue<int> que;
bool vis[T];

void dij(int cnum) {
	priority_queue<pair<ll, int> > q;
	for (int i = 0; i < (int)co[cnum].size(); i++) {
		q.push(make_pair(-dis[co[cnum][i]], co[cnum][i]));
	}
	while(!q.empty()) {
		pair<ll, int> pr = q.top(); q.pop();
		int x = pr.second;
		if (vis[x]) continue;
		vis[x] = true;
		for (int i = head[x]; i; i = Next[i]) {
			int y = ver[i]; ll z = edge[i];
			if (c[y] == c[x]) {
				if (dis[y] > dis[x] + z) {
					dis[y] = dis[x] + z;
					q.push(make_pair(-dis[y], y));
				}
			}
			else {
				if (dis[x] != INF) { //否则dis[x] ==INF时,dis[y]仍然可能变小
					dis[y] = min(dis[y], dis[x] + z);
				}
				indeg[c[y]]--;
				if (indeg[c[y]] == 0) {
					que.push(c[y]);
				}
			}
		}
	}
	
}

void topsort() {
	memset(dis, 0x3f3f3f3f, sizeof(dis));
	memset(vis, false, sizeof(vis));
	dis[s] = 0;

	for (int i = 1; i <= totc; i++) {
		if (indeg[i] == 0) {
			que.push(i);
		}
	}
	while(!que.empty()) {
		dij(que.front());
		que.pop();
	}

}

int main() {
	ios::sync_with_stdio(false);
	cin >> t >> r >> p >> s;
	while(r--) {
		int a, b; ll c;
		cin >> a >> b >> c;
		add(a, b, c); add(b, a, c);
	}

	divi::divi_c();

	while(p--) {
		int a, b; ll z;
		cin >> a >> b >> z;
		add(a, b, z);
		if (c[a] != c[b]) {
			indeg[c[b]]++;
		}
	}

	topsort();

	for (int i = 1; i <= t; i++) {
		if (dis[i] == INF) {
			cout << "NO PATH" << "\n";
		}
		else 
			cout << dis[i] << "\n";
	}


}	

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值