You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

141 lines
5.5 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <bits/stdc++.h>
using namespace std;
const int N = 25010, M = 150010;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> PII;
// 存图
int idx, h[N], e[M], w[M], ne[M];
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int T; // 城镇数量
int R; // 道路数量
int P; // 航线数量
int S; // 出发点
// 下面两个数组是一对
int id[N]; // 节点在哪个连通块中
vector<int> block[N]; // 连通块包含哪些节点
int bcnt; // 连通块序号计数器
int dis[N]; // 最短距离(结果数组)
int in[N]; // 每个DAG(节点即连通块)的入度
bool st[N]; // dijkstra用的是不是在队列中的数组
queue<int> q; // 拓扑序用的队列
// 将u节点加入团中,团的番号是 bid
void dfs(int u, int bid) {
id[u] = bid; // ① u节点属于bid团
block[bid].push_back(u); // ② 记录bid团包含u节点
// 枚举u节点的每一条出边将对端的城镇也加入到bid这个团中
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (!id[j]) dfs(j, bid); // Flood Fill
}
}
// 计算得到bid这个连通块中最短距离
void dijkstra(int bid) {
priority_queue<PII, vector<PII>, greater<PII>> pq;
/*
因为不确定连通块内的哪个点可以作为起点,所以就一股脑全加进来就行了,
反正很多点的dis都是inf这些都是不能成为起点的,那么可以作为起点的就自然出现在堆顶了
因为上面的写法把拓扑排序和dijkstra算法拼在一起了如果不把所有点都加入堆
会导致后面其他块的din[]没有减去前驱边,从而某些块没有被拓扑排序遍历到。
*/
for (auto u : block[bid]) pq.push({dis[u], u});
while (pq.size()) {
int u = pq.top().second;
pq.pop();
if (st[u]) continue;
st[u] = true;
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (st[v]) continue;
if (dis[v] > dis[u] + w[i]) {
dis[v] = dis[u] + w[i];
// 如果是同团中的道路,需要再次进入Dijkstra的小顶堆以便计算完整个团中的路径最小值
if (id[u] == id[v]) pq.push({dis[v], v});
}
/*如果u和v不在同一个团中,说明遍历到的是航线
此时,需要与拓扑序算法结合,尝试剪掉此边,是不是可以形成入度为的团
id[v]:v这个节点所在的团番号
--in[id[v]] == 0: u->v是最后一条指向团id[v]的边此边拆除后id[v]这个团无前序依赖,稳定了,
可以将此团加入拓扑排序的queue队列中继续探索
*/
if (id[u] != id[v] && --in[id[v]] == 0) q.push(id[v]);
}
}
}
// 拓扑序
void topsort() {
for (int i = 1; i <= bcnt; i++) // 枚举每个团
if (!in[i]) q.push(i); // 找到所有入度为0的团,DAG的起点
// 拓扑排序
while (q.size()) {
int bid = q.front(); // 团番号
q.pop();
// 在此团内部跑一遍dijkstra
dijkstra(bid);
}
}
int main() {
memset(h, -1, sizeof h); // 初始化
scanf("%d %d %d %d", &T, &R, &P, &S); // 城镇数量,道路数量,航线数量,出发点
memset(dis, 0x3f, sizeof dis); // 初始化最短距离
dis[S] = 0; // 出发点距离自己的长度是0,其它的最短距离目前是INF
int a, b, c; // 起点,终点,权值
while (R--) { // 读入道路,团内无向图
scanf("%d %d %d", &a, &b, &c);
add(a, b, c), add(b, a, c); // 连通块内是无向图
}
/* 航线本质是 团与团 之间单向连接边
外部是DAG有向无环图局部是内部双向正权图
为了建立外部的DAG有向无环图我们需要给每个团分配一个番号记为bid;
同时,也需要知道每个团内,有哪些小节点:
(1) id[i]:节点i隶属于哪个团(需要提前准备好团的番号)
(2) vector<int> block[N] :每个团中有哪些节点
Q:一共几个团呢?每个团中都有谁呢?谁都在哪个图里呢?
A:在没有录入航线的情况下,现在图中只有 大块孤立 但 内部连通 的节点数据,
可以用dfs进行Flood Fill,发现没有团标识的节点,就创建一个新的团番号,
并且记录此节点加入了哪个团,记录哪个团有哪些点。
注意:需要在未录入航线的情况下统计出团与节点的关系,否则一会再录入航线,就没法找出哪些节点在哪个团里了
*/
// 缩点
for (int i = 1; i <= T; i++) // 枚举每个小节点
if (!id[i]) // 如果它还没有标识是哪个团,就开始研究它,把它标识上隶属于哪个团,并且,把和它相连接的其它点也加入同一个团中
dfs(i, ++bcnt); // 需要提前申请好番号bcnt
// 航线
while (P--) {
scanf("%d %d %d", &a, &b, &c);
add(a, b, c); // 单向边
in[id[b]]++; // b节点所在团的番号,也就是某个团的入度+1
}
// 拓扑
topsort();
// 从S到达城镇i的最小花费
for (int i = 1; i <= T; i++) {
if (dis[i] > INF / 2)
puts("NO PATH");
else
cout << dis[i] << endl;
}
return 0;
}