P1967 [NOIP2013提高组] 货车运输
解题思路
本题需要解决的是在带权无向图中,找到两点间路径的最小边权的最大值(即最大瓶颈路)。核心步骤包括:
-
构建最大生成树:使用Kruskal算法,确保路径上的最小边权尽可能大
-
预处理LCA和路径最小值:使用倍增法预处理每个节点的祖先信息和路径最小边权
-
查询处理:对于每个查询,通过LCA找到路径并确定最小边权
#include<bits/stdc++.h> #define pii pair<int,int> using namespace std; const int N = 1e4 + 10, M = 5e4 + 10, inf = 0x3f3f3f3f; // 边结构体 struct node { int x, y, z; // 起点、终点、限重 }; node a[M]; // 存储所有边 vector<pii> g[N]; // 最大生成树的邻接表 int n, m, q; int dep[N]; // 节点深度 int fa[N][26]; // 倍增祖先表 int dp[N][26]; // 存储向上跳2^i步的路径最小边权 int f[N]; // 并查集父节点 int vis[N]; // 访问标记 // 按边权从大到小排序 bool cmp(node a, node b) { return a.z > b.z; } // 并查集查找 int find(int x) { if(f[x] != x) f[x] = find(f[x]); return f[x]; } // 并查集合并 void merge(int x, int y) { int fx = find(x), fy = find(y); f[fy] = fx; } // Kruskal算法构建最大生成树 void kruskal() { sort(a + 1, a + 1 + m, cmp); // 按边权降序排序 for(int i = 1; i <= n; i++) f[i] = i; // 初始化并查集 for(int i = 1; i <= m; i++) { int x = a[i].x, y = a[i].y; if(find(x) != find(y)) { // 如果不在同一集合 merge(x, y); // 合并集合 g[x].push_back({y, a[i].z}); // 添加到生成树 g[y].push_back({x, a[i].z}); } } } // DFS预处理LCA和路径最小值 void dfs(int x, int rt, int d) { dep[x] = dep[rt] + 1; // 计算深度 fa[x][0] = rt; // 直接父节点 dp[x][0] = d; // 到父节点的边权 vis[x] = 1; // 标记已访问 // 预处理倍增表 for(int i = 1; i <= 20; i++) { int y = fa[x][i - 1]; // 中间节点 fa[x][i] = fa[y][i - 1]; // 祖先关系 dp[x][i] = min(dp[x][i - 1], dp[y][i - 1]); // 路径最小值 } // 遍历邻接节点 for(int i = 0; i < g[x].size(); i++) { int y = g[x][i].first, z = g[x][i].second; if(y != rt && !vis[y]) { // 不是父节点且未访问 dfs(y, x, z); // 递归处理 } } } // LCA查询并计算路径最小边权 int lca(int x, int y) { int ans = inf; // 初始化最小边权 // 确保x是较深的节点 if(dep[x] < dep[y]) swap(x, y); // 将x提升到与y同一深度 for(int i = 20; i >= 0; i--) { if(dep[fa[x][i]] >= dep[y]) { ans = min(ans, dp[x][i]); // 更新路径最小值 x = fa[x][i]; // 跳跃 } } if(x == y) return ans; // 如果已经是同一节点 // 同时提升x和y for(int i = 20; i >= 0; i--) { if(fa[x][i] != fa[y][i]) { // 祖先不同才跳跃 ans = min(ans, min(dp[x][i], dp[y][i])); // 更新最小值 x = fa[x][i]; y = fa[y][i]; } } // 最后一步到LCA的边 ans = min(ans, min(dp[x][0], dp[y][0])); return ans; } int main() { memset(dp, inf, sizeof(dp)); // 初始化DP表 cin >> n >> m; for(int i = 1; i <= m; i++) cin >> a[i].x >> a[i].y >> a[i].z; kruskal(); // 构建最大生成树 // 预处理每个连通分量 for(int i = 1; i <= n; i++) if(vis[i] == 0) dfs(i, 0, inf); // 根节点的父节点设为0 cin >> q; while(q--) { int x, y; cin >> x >> y; if(find(x) != find(y)) // 不在同一连通块 cout << -1 << endl; else cout << lca(x, y) << endl; // 输出路径最小边权 } return 0; }
算法分析
-
时间复杂度:
-
Kruskal算法:O(MlogM) 排序边 + O(Mα(N)) 并查集操作
-
DFS预处理:O(NlogN) 每个节点处理20级祖先
-
查询处理:每个查询O(logN)
-
-
空间复杂度:
-
O(NlogN) 存储倍增表
-
O(N) 存储其他信息
-
-
关键点:
-
最大生成树确保路径最小边权最大化
-
倍增法高效查询LCA和路径信息
-
并查集处理连通性
-

浙公网安备 33010602011771号