有一个局域网,由 n 个设备和 m 条物理连接组成,第 i 条连接的稳定性为wi 。
对于从设备 A 到设备 B 的一条经过了若干个物理连接的路径,我们记这条路径的稳定性为其经过所有连接中稳定性最低的那个。
我们记设备 A 到设备 B 之间通信的稳定性为 A 至 B 的所有可行路径的稳定性中最高的那一条。
给定局域网中的设备的物理连接情况,求出若干组设备 xi 和 yi 之间的通信稳定性。如果两台设备之间不存在任何路径,请输出 −1 。
输入格式
输入的第一行包含三个整数 n, m, q ,分别表示设备数、物理连接数和询问数。
接下来 m 行,每行包含三个整数 ui , vi ,wi ,分别表示 ui 和 vi 之间有一条稳定性为 wi 的物理连接。
接下来 q 行,每行包含两个整数 xi , yi ,表示查询 xi 和 yi 之间的通信稳定性。
输出格式
输出 q 行,每行包含一个整数依次表示每个询问的答案。
样例输入
5 4 3
1 2 5
2 3 6
3 4 1
1 4 3
1 5
2 4
1 3
样例输出
-1
3
5
提示
对于 30% 的评测用例,n, q ≤ 500,m ≤ 1000 ;
对于 60% 的评测用例,n, q ≤ 5000,m ≤ 10000 ;
对于所有评测用例,2 ≤ n, q ≤ 1e5,1 ≤ m ≤ 3 × 1e5,1 ≤ ui , vi , xi , yi ≤ n,
1 ≤ wi ≤ 1e6,ui ≠ vi,xi ≠ yi 。
这道题其实是一个经典的最大生成树 + 最近公共祖先 (LCA) 问题。我们来拆解一下逻辑:
1. 核心思路:最大生成树
题目要求路径上"最小权值的最大值"。
想象一下,如果你想从 A 到 B 走一条尽可能"稳"的路,你肯定优先选权值大的边。当你按照边权从大到小去建立一棵树(直到 A 和 B 连通)时,这条树上路径的最小边权,就是你要的答案。
-
结论 :任意两点间的"最大稳定性路径"一定在最大生成树上。
-
做法:使用 Kruskal 算法(把边权排序后反过来取),构建一棵最大生成树。
2. 树上查询:LCA 与 路径最小值
在一棵树上,两点 u, v 之间的唯一路径经过它们的 LCA。
我们需要求出这条路径上权值最小的边。我们可以利用倍增法 在计算 LCA 的同时,维护一个 min_w[u][i] 数组,表示从 u 向上跳 2^i 层这段路径上的最小边权。
3. C++ 实现步骤
第一步:Kruskal 构建最大生成树
使用并查集保证不形成环,优先连权重大的边。
第二步:DFS 预处理
求出深度 deep[],以及倍增数组 fa[u][i](祖先)和 min_v[u][i](路径最小值)。
第三步:倍增 LCA 查询
跳跃过程中不断更新最小值。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN = 100005;
const int LOGN = 18;
const int INF = 1e9;
struct Edge {
int u, v, w;
};
bool compareEdges(Edge a, Edge b) {
return a.w > b.w; // 最大生成树,按权值降序
}
struct Node {
int to, w;
};
vector<Node> adj[MAXN];
int fa[MAXN][LOGN + 1], min_v[MAXN][LOGN + 1];
int depth[MAXN], p[MAXN];
// 并查集
int find(int x) {
if (p[x] == x) return x;
return p[x] = find(p[x]);
}
void dfs(int u, int father, int w, int d) {
depth[u] = d;
fa[u][0] = father;
min_v[u][0] = w;
for (int i = 1; i <= LOGN; i++) {
fa[u][i] = fa[fa[u][i - 1]][i - 1];
min_v[u][i] = min(min_v[u][i - 1], min_v[fa[u][i - 1]][i - 1]);
}
for (auto &edge : adj[u]) {
if (edge.to != father) {
dfs(edge.to, u, edge.w, d + 1);
}
}
}
int query(int u, int v) {
if (find(u) != find(v)) return -1; // 不连通
int res = INF;
if (depth[u] < depth[v]) swap(u, v);
// 先跳到同一深度
for (int i = LOGN; i >= 0; i--) {
if (depth[fa[u][i]] >= depth[v]) {
res = min(res, min_v[u][i]);
u = fa[u][i];
}
}
if (u == v) return res;
// 一起向上跳
for (int i = LOGN; i >= 0; i--) {
if (fa[u][i] != fa[v][i]) {
res = min(res, min(min_v[u][i], min_v[v][i]));
u = fa[u][i];
v = fa[v][i];
}
}
return min(res, min(min_v[u][0], min_v[v][0]));
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n, m, q;
cin >> n >> m >> q;
vector<Edge> edges(m);
for (int i = 0; i < m; i++) cin >> edges[i].u >> edges[i].v >> edges[i].w;
sort(edges.begin(), edges.end(), compareEdges);
for (int i = 1; i <= n; i++) p[i] = i;
int count = 0;
for (auto &e : edges) {
int rootU = find(e.u), rootV = find(e.v);
if (rootU != rootV) {
p[rootU] = rootV;
adj[e.u].push_back({e.v, e.w});
adj[e.v].push_back({e.u, e.w});
if (++count == n - 1) break;
}
}
// 处理森林情况(可能不连通)
for (int i = 1; i <= n; i++) {
if (!depth[i]) dfs(i, 0, INF, 1);
}
while (q--) {
int x, y;
cin >> x >> y;
cout << query(x, y) << "\n";
}
return 0;
}