思路
虽然有负边,不能直接求最短路,但是观察到边是分为两种的,一种是无向、非负边,另一种则是有向边,可能为负。单单是这样,和只有有向边的情况也差不多,更重要的是另一个条件,若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";
}
}