Description
给你 \(n\) 个点 \(m\) 条边的有向图,求给定 \(k\) 个特殊点两两之间最短路的最小值。\(T\) 组询问。
\(1 \leq T \leq 5\)
\(1 \leq k \leq n \leq 10^5\)
\(1 \leq m \leq 5 \times 10^5\)
\(1 \leq z \leq 2 \times 10^9\)
Solution
两种思路。
第一种:
对于每一条边 \((x,y,z)\) ,离 \(x\) 最近的特殊点是 \(a\),离 \(y\) 最近的特殊点是 \(b\)。那么,这两个特殊点的距离是 \(dis_a + dis_b + z\)。我们要找到最小值。
因为这是有向图,所以正反跑两次 Dijkstra 就可以了进行染色就可以了,染色是记录离每个城市最近的感兴趣的城市。如果 \(a = b\),那么就相当于走了一个环,所以我们要特判。
复杂度 \(O(T \times nlogn)\)。
第二种:
将所有的点分成两个集合 \(A\) 和 \(B\)。我们用源点 \(s\) 连接集合 \(A\) 中所有的点,边权都为 \(0\),再将集合 \(B\) 连向汇点 \(t\),边权都为 \(0\)。集合内部边权不变。
将所有节点分成两个集合,要满足任何两个特殊点都有至少一次被分进了不同的集合。可以按照每个点的编号的二进制的第 \(i\) 位是 \(0\) 还是 \(1\) 枚举。只要是两个不同的点,一定会被分进两个不同的集合。同样,因为也是有向图,也要跑两边 Dijkstra。
复杂度 \(O(T \times nlog^2n)\)。
开 \(long \ long\),注意无穷大的值,注意 \(<\) 的重载。两遍 Dijkstra 时间复杂度低,代码实现容易,所以只写两边 Dijkstra做法。二进制枚举也可行,但只拓宽思路而不推荐。
Code
#include <bits/stdc++.h> using namespace std; #define re register #define F first #define S second typedef long long ll; typedef pair<int, int> P; const int N = 1e6 + 6; const int INF = 0x3f3f3f3f; inline int read() { int X = 0,w = 0; char ch = 0; while(!isdigit(ch)) {w |= ch == '-';ch = getchar();} while(isdigit(ch)) X = (X << 3) + (X << 1) + (ch ^ 48),ch = getchar(); return w ? -X : X; } int Start[N], To[N], Dis[N], color[2][N], a[N], head[N], tot; ll dis[2][N]; int n, m, k; struct edge{ int to, w, nxt; }e[N]; inline void addedge(int x, int y, int z){ e[++tot].to = y; e[tot].w = z; e[tot].nxt = head[x]; head[x] = tot; } struct node{ int x; ll w; }; bool operator < (node x, node y){ return x.w > y.w; } void Dijkstra(int p){ memset(color[p], 0, sizeof(color[p])); memset(dis[p], 0x7f, sizeof(dis[p])); priority_queue <node> q; for (re int i = 1; i <= k; i++) { dis[p][a[i]] = 0; color[p][a[i]] = a[i]; q.push((node){a[i], 0}); } while (!q.empty()){ int x = q.top().x; ll w = q.top().w; q.pop(); if (w != dis[p][x]) continue; for (re int i = head[x]; i; i = e[i].nxt){ int y = e[i].to, ww = e[i].w; if (dis[p][y] > dis[p][x] + ww){ dis[p][y] = dis[p][x] + ww; color[p][y] = color[p][x]; q.push((node){y, dis[p][y]}); } } } } int main(){ int T = read(); while (T--){ n = read(), m = read(), k = read(); memset(head, 0, sizeof(head)); tot = 0; for (re int i = 1; i <= m; i++){ int x = read(), y = read(), z = read(); Start[i] = x; To[i] = y; Dis[i] = z; if (x != y) addedge(x, y, z); } for (re int i = 1; i <= k; i++) a[i] = read(); Dijkstra(0); memset(head, 0, sizeof(head)); tot = 0; for (int i = 1; i <= m; i++) if (Start[i] != To[i]) addedge(To[i], Start[i], Dis[i]); Dijkstra(1); ll ans = 1e18; for (re int i = 1; i <= m; i++){ int x = Start[i], y = To[i], z = Dis[i]; if (color[0][x] && color[1][y] && color[0][x] != color[1][y]) ans = min(ans, z + dis[0][x] + dis[1][y]); } printf("%lld\n", ans); } return 0; }