洛谷 P3008. Roads and Planes G
题目描述
分析
题意:NO PATH。
难点:图中存在负权边,Dijkstra 的贪心会失效;而 SPFA 在本题的数据规模下会被卡。题目给出了一个关键保证:如果存在航线
思路:连通块 + 拓扑排序 + 块内 Dijkstra
既然航线图是 DAG,就可以借助拓扑序把问题"分而治之":
划分连通块:只加双向道路,用 DFS 求出每个点所在的连通块编号
,并记录每个块包含哪些点。 建 DAG:加回单向航线,统计每个块的入度——航线
使块 的入度加 。 拓扑序 + 块内 Dijkstra:入度为
的块先入队。处理块 时,把块内所有点放进堆里跑 Dijkstra: - 块内边全是非负权道路,Dijkstra 的贪心成立;
- 松弛到同块的点
时,更新后正常入堆; - 遇到指向其他块的点
的边时,若更优则更新 ,但不入堆;同时把块 的入度减 。由于每个点只会出堆一次,每条航线恰好被处理一次,入度减到 的块就可以入拓扑队列。
这样保证:处理某个块时,所有指向它的航线产生的松弛都已完成,之后不会被更小的值再次更新,因此每个点出队时
就是最终最短路。 判断不可达:负权航线可能把初始的
更新成略小的值,所以不能仅凭 是否仍等于初始值来判断,而应检查 ,满足则输出 NO PATH。
正确性:块内 Dijkstra 处理非负道路,块间拓扑序保证负权航线的松弛按依赖顺序进行,两者结合即为全局最短路。
复杂度:每条边至多被松弛一次,总复杂度
参考代码
cpp
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 25010, M = 150010;
int h[N], e[M], w[M], ne[M], idx;
int c[N], deg[N];
vector<int> b[N];
int bs; // 连通块的数量
int dist[N];
bool st[N];
int t, r, p, s;
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int start, int id) {
c[start] = id;
b[id].push_back(start);
for (int i = h[start]; i != -1; i = ne[i]) {
int j = e[i];
if (c[j]) continue;
dfs(j, id);
}
}
void dijkstra(int id, queue<int>& q) {
priority_queue<PII, vector<PII>, greater<PII>> heap;
for (auto x : b[id]) {
heap.push({dist[x], x});
}
while (heap.size()) {
auto k = heap.top();
heap.pop();
if (st[k.second]) continue;
st[k.second] = 1;
for (int i = h[k.second]; i != -1; i = ne[i]) {
int j = e[i];
if (dist[j] > k.first + w[i]) {
dist[j] = k.first + w[i];
if (c[k.second] == c[j]) heap.push({dist[j], j});
}
if (c[k.second] != c[j]) {
deg[c[j]] -= 1;
if (deg[c[j]] == 0) {
q.push(c[j]);
}
}
}
}
}
int main() {
memset(h, -1, sizeof h);
cin >> t >> r >> p >> s;
for (int i = 0; i < r; i++) {
int a, b, c; cin >> a >> b >> c;
add(a, b, c); add(b, a, c);
}
for (int i = 1; i <= t; i++) {
if (!c[i]) {
bs++;
dfs(i, bs);
}
}
for (int i = 0; i < p; i++) {
int x, y, z; cin >> x >> y >> z;
add(x, y, z);
deg[c[y]]++;
}
// topsort
memset(dist, 0x3f, sizeof dist);
dist[s] = 0;
queue<int> q;
for (int i = 1; i <= bs; i++) {
if (!deg[i]) q.push(i);
}
while (q.size()) {
int k = q.front();
q.pop();
dijkstra(k, q);
}
for (int i = 1; i <= t; i ++ ) {
if (dist[i] > 0x3f3f3f3f / 2) cout << "NO PATH" << endl;
else cout << dist[i] << endl;
}
return 0;
}