CF2117G 并查集
CF2117G 题解
题目重述
给定一个带权无向连通图,求从结点 \(1\) 到结点 \(n\) 的所有路径中,路径费用最小的值。路径费用定义为:路径上的最大边权 + 最小边权。路径可以是非简单路径(允许重复经过边或结点)。
解题思路
由于走的路径一定是一棵树,我们要找的是树上的最大和最小的和,但单纯的\(Kruskal\)并不一定能得到最优解,在\(1\)与\(n\)刚连通时的答案也不一定最优。
所以我们使用\(Kruskal\)算法的变种,通过以下步骤解决问题:
- 边排序:将所有边按权值从小到大排序。
- 逐步加边:使用并查集维护连通性。
- 维护最小值:记录每个连通块中的最小边权。
- 计算候选答案:当\(1\)和\(n\)连通时,用当前连通块的最小值加上当前边的权值(最大值)作为候选答案。
不明白的可以手搓一下以下案例:

输入:
1
6 5
1 6 15
1 3 17
3 4 10
4 5 25
5 2 8
本题有多测,我的代码不用清空数组,wa了的可以看一下是不是忘了
n为6,如果只看是否连到最小边,会连到2,值为33
连通 \(1\) \(3\) \(4\) \(6\) 时最小,为\(27\)。
复杂度分析
- 时间复杂度:\(O(m \log m)\)
- 空间复杂度:\(O(n + m)\)
CODE
#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn = 2e5+10; // 最大节点数
constexpr int maxm = 4e5+10; // 最大边数
constexpr int INF = LONG_LONG_MAX>>1;
typedef struct edge {
int fr, to, wei; // 起点、终点、权重
// 重载运算符
bool operator<(const edge& other)const {
return wei < other.wei;
}
}edge;
edge edges[maxn]; // 边的数组
int fa[maxn]; // 并查集的父节点数组
int min_edge[maxn]; // 连通块中的最小边权
int find_root(int x) {
if(x != fa[x]) {
fa[x] = find_root(fa[x]);
}
return fa[x];
}
void init_fa(int n) {
for(int i = 1; i <= n; ++i) {
fa[i] = i;
min_edge[i] = INF; // 初始最小边权为无穷大
}
}
int kruskal(int n, int m) {
sort(edges, edges + m);
init_fa(n);
int ans = INF;
for (int i = 0; i < m; ++i) {
int xr = find_root(edges[i].fr);
int yr = find_root(edges[i].to);
if(xr != yr) {
fa[xr] = yr;
}
// 更新当前连通块的最小边权-min(将要相连的两块的最小值,当前边的值)
min_edge[yr] = min({min_edge[xr], min_edge[yr], edges[i].wei});
// 检查节点1和n是否连通
if(find_root(1) == find_root(n)) {
// 如果连通,则更新答案:当前边权重(最大)+连通块最小边权
// 连通后不用再加特判谁是最大边,因为当前边会不断增大
// 只有1的并集(已经链接到n)的最小值被修改了,它的值才可能比原来的ans小。
ans = min(ans, edges[i].wei + min_edge[find_root(1)]);
}
}
return ans;
}
signed main() {
int t;
int n = 0, m;
scanf("%lld", &t);
while(t--) {
scanf("%lld%lld", &n, &m);
for(int i = 0; i < m; ++i) {
scanf("%lld%lld%lld", &edges[i].fr, &edges[i].to, &edges[i].wei);
}
int ans = kruskal(n, m);
printf("%lld\n", ans);
}
return 0;
}

浙公网安备 33010602011771号