原题链接:https://nanti.jisuanke.com/t/42388
题意
有n个点,x条双向边且边权为正,y条单向边边权可以为负,问从某个点出发到全部点的最短距离。
分析
首先看到负权边,dij不能直接去用。
然后就是这题刻意去卡了spfa,但没卡住spfa的玄学优化(可以自己去尝试)。
这里说一下本题的正解,利用强连通分量和最短路来求。
既然dij不能跑负权边,那我们只要保证让他只跑正权边就可以了,因此在加完双向边之后去找连通块,每个连通块内部跑dij,然后外部用拓扑。
这里说两个细节
- 开始要把所有入度为0的连通块加进去,因为存在这样一种情况,如果a可以到b,c可以到b,但我们从a出发,不将c入队列,那么b的度永远不会为0,拓扑无法进行下去。
- 在跑dij时要把所有连通块内的点都加进堆中,因为我们不知道从哪个点开始是最优,因此全部加进去松弛。
Code
#include <bits/stdc++.h>
using namespace std;
//#define ACM_LOCAL
const int N = 3e5 + 10;
const int M = 1e6 + 10;
const int INF = 0x3f3f3f3f;
const double eps = 1e-4;
const int MOD = 59964251;
typedef long long ll;
typedef pair<int, int> PII;
struct Edge {
int to, next, w;
}e[M];
int cnt, h[N], vis[N], idx, low[N], dfn[N], in_stk[N], tp, scc, sd[N];
int dis[N], in[N];
vector<int> ve[N];
void add(int u, int v, int w) {
e[cnt].to = v;
e[cnt].w = w;
e[cnt].next = h[u];
h[u] = cnt++;
}
void tarjan(int x) {
low[x] = dfn[x] = ++idx;
vis[x] = 1;
in_stk[++tp] = x;
for (int i = h[x]; ~i; i = e[i].next) {
int v = e[i].to;
if (!dfn[v]) {
tarjan(v);
low[x] = min(low[x], low[v]);
}
else if (vis[v]) {
low[x] = min(low[x], dfn[v]);
}
}
if (low[x] == dfn[x]) {
int y;
++scc;
while(y = in_stk[tp--]) {
sd[y] = scc;
vis[y] = 0;
ve[scc].push_back(y);
if (x == y) break;
}
}
}
struct node {
int d, now;
bool operator < (const node &rhs) const {
return d > rhs.d;
}
};
queue<int> q;
void dij(int s) {
priority_queue<node> pq;
for (int i = 0; i < ve[s].size(); i++) pq.push({dis[ve[s][i]], ve[s][i]});
while (pq.size()) {
int now = pq.top().now;
pq.pop();
if (vis[now]) continue;
vis[now] = 1;
for (int i = h[now]; ~i; i = e[i].next) {
int v = e[i].to;
if (dis[v] > dis[now] + e[i].w) {
dis[v] = dis[now] + e[i].w;
if (sd[v] == sd[now] && !vis[v]) {
pq.push({dis[v], v});
}
}
in[sd[v]]--;
if (!in[sd[v]] && sd[now] != sd[v]) {
q.push(sd[v]);
}
}
}
}
void tupo(int s, int n) {
memset(dis, 0x3f, sizeof dis);
memset(vis, 0, sizeof vis);
dis[s] = 0; q.push(sd[s]);
for (int i = 1; i <= scc; i++) if (!in[i]) q.push(i);
while (q.size()) {
int now = q.front();
q.pop();
dij(now);
}
}
void solve() {
int n, x, y, s;
scanf("%d%d%d%d", &n, &x, &y, &s);
memset(h, -1, sizeof h);
int u, v, w;
for (int i = 1; i <= x; i++) {
scanf("%d%d%d", &u, &v, &w);
add(u, v, w), add(v, u, w);
}
for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
for (int i = 1; i <= y; ++i) {
scanf("%d%d%d", &u, &v, &w);
in[sd[v]]++;
add(u, v, w);
}
tupo(s, n);
for (int i = 1; i <= n; ++i) {
if (dis[i] <= 1e9) printf("%d\n", dis[i]);
else printf("NO PATH\n");
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
#ifdef ACM_LOCAL
freopen("input", "r", stdin);
freopen("output", "w", stdout);
#endif
solve();
}