GESP认证C++编程真题解析 | 202509 八级
欢迎大家订阅我的CSDN专栏:算法题解:C++与Python实现!
本专栏旨在帮助大家从基础到进阶 ,逐步提升编程能力,助力信息学竞赛备战!
专栏特色
1.经典算法练习:根据信息学竞赛大纲,精心挑选经典算法题目,提供清晰的代码实现与详细指导,帮助您夯实算法基础。
2.系统化学习路径:按照算法类别和难度分级,从基础到进阶,循序渐进,帮助您全面提升编程能力与算法思维。
适合人群:
- 准备参加蓝桥杯、GESP、CSP-J、CSP-S等信息学竞赛的学生
- 希望系统学习C++/Python编程的初学者
- 想要提升算法与编程能力的编程爱好者
附上汇总帖:GESP认证C++编程真题解析 | 汇总
编程题
P14080 最小生成树
【题目来源】
洛谷:[P14080 GESP202509 八级] 最小生成树 - 洛谷
【题目描述】
给定一张包含 \(n\) 个结点 \(m\) 条边的带权连通无向图,结点依次以 \(1,2,\ldots,n\) 编号,第 \(i\) 条边(\(1\le i\le m\))连接结点 \(u_i\) 与结点 \(v_i\),边权为 \(w_i\)。
对于每条边,请你求出从图中移除该条边后,图的最小生成树中所有边的边权和。特别地,若移除某条边后图的最小生成树不存在,则输出 \(-1\)。
【输入】
第一行,两个正整数 \(n,m\),分别表示图的结点数与边数。
接下来 \(m\) 行中的第 \(i\) 行(\(1\le i\le m\))包含三个正整数 \(u_i,v_i,w_i\),表示图中连接结点 \(u_i\) 与结点 \(v_i\) 的边,边权为 \(w_i\)。
【输出】
输出共 \(m\) 行,第 \(i\) 行(\(1\le i\le m\))包含一个整数,表示移除第 \(i\) 条边后,图的最小生成树中所有边的边权和。若移除第 \(i\) 条边后图的最小生成树不存在,则输出 \(−1\)。
【输入样例】
5 5
1 2 4
2 3 3
3 4 1
2 5 2
3 1 8
【输出样例】
14
15
-1
-1
10
【解题思路】

【算法标签】
《洛谷 P14080 最小生成树》 #线段树# #倍增# #并查集# #生成树# #最近公共祖先LCA# #树链剖分# #GESP# #2025#
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 使用长整型防止溢出
const int N = 100005; // 最大节点数和边数
// 边结构体
struct Edge
{
int a, b, w, idx; // a:起点, b:终点, w:权重, idx:边的编号
// 重载小于运算符,按权重排序
bool operator< (const Edge &t) const
{
return w < t.w;
}
} e[N], nt[N]; // e:所有边, nt:非树边(不在最小生成树中的边)
int n, m; // n:节点数, m:边数
int u, v, w, idx; // 临时变量
int cur; // 非树边计数器
int ans; // 最小生成树的总权重
int ids[N]; // 存储每个节点到根路径上的最大非树边权重
int p[N]; // 并查集数组(用于Kruskal算法)
int fa[N]; // 存储每个节点的父节点(用于DFS树)
int deep[N]; // 存储每个节点的深度
bool etree[N]; // 标记边是否在最小生成树中
vector<Edge> g[N]; // 最小生成树的邻接表
/**
* 并查集查找操作(带路径压缩)
* @param x 要查找的节点
* @return 节点x的根节点
*/
int find(int x)
{
if (p[x] != x)
{
p[x] = find(p[x]); // 路径压缩
}
return p[x];
}
/**
* DFS遍历最小生成树,构建父节点和深度信息
* @param fx 父节点
* @param x 当前节点
* @param d 当前深度
*/
void dfs(int fx, int x, int d)
{
if (fx == 0)
{
fa[x] = x; // 根节点的父节点指向自己
}
else
{
fa[x] = fx; // 记录父节点
}
deep[x] = d; // 记录深度
// 遍历所有邻接边
for (int i = 0; i < g[x].size(); i++)
{
// 跳过父节点,避免回边
if (g[x][i].b == fx)
{
continue;
}
dfs(x, g[x][i].b, d + 1);
}
}
/**
* 比较函数:按边的原始编号排序
*/
bool cmp(Edge x, Edge y)
{
return x.idx < y.idx;
}
signed main() // 使用signed代替int(因为定义了#define int long long)
{
// 输入节点数和边数
cin >> n >> m;
// 输入所有边的信息
for (int i = 1; i <= m; i++)
{
cin >> e[i].a >> e[i].b >> e[i].w;
e[i].idx = i; // 记录边的原始编号
}
// ========== Kruskal算法构建最小生成树 ==========
// 初始化并查集
for (int i = 1; i <= n; i++)
{
p[i] = i;
}
// 将边按权重从小到大排序
sort(e + 1, e + m + 1);
// 构建最小生成树
for (int i = 1; i <= m; i++)
{
int a = e[i].a, b = e[i].b, w = e[i].w, idx = e[i].idx;
// 如果两个节点不在同一个连通分量中
if (find(a) != find(b))
{
// 合并两个连通分量
p[find(a)] = find(b);
// 标记该边在最小生成树中
etree[idx] = 1;
// 将边添加到最小生成树的邻接表中
g[a].push_back({0, b, w, idx});
g[b].push_back({0, a, w, idx});
// 累加最小生成树的总权重
ans += w;
}
else
{
// 该边不在最小生成树中,记录为非树边
nt[++cur] = e[i];
continue;
}
}
// 检查图是否连通
for (int i = 1; i <= n; i++)
{
if (find(1) != find(i))
{
// 图不连通,输出-1
for (int j = 1; j <= m; j++)
{
cout << -1 << endl;
}
return 0;
}
}
// ========== 预处理每个节点到根路径上的最大非树边权重 ==========
// 重新初始化并查集
for (int i = 1; i <= n; i++)
{
p[i] = i;
ids[i] = -1; // 初始化为-1,表示未知
}
// 从节点1开始DFS遍历最小生成树,构建父节点和深度信息
dfs(0, 1, 1);
// 处理所有非树边,记录路径上的最大权重
for (int i = 1; i <= cur; i++)
{
int a = find(nt[i].a), b = find(nt[i].b), w = nt[i].w;
// 找到两个节点在树上的LCA路径
while (a != b)
{
// 选择深度较大的节点向上跳
if (deep[a] < deep[b])
{
swap(a, b);
}
// 记录该节点路径上的最大非树边权重
ids[a] = w;
// 将当前节点合并到父节点所在的集合
p[a] = find(fa[a]);
a = find(a);
}
}
// ========== 计算次小生成树 ==========
// 将边按原始编号排序,以便按输入顺序输出
sort(e + 1, e + m + 1, cmp);
// 对每条边计算次小生成树权重
for (int i = 1; i <= m; i++)
{
if (!etree[i])
{
// 非树边:次小生成树权重等于最小生成树权重
cout << ans << endl;
}
else
{
// 树边:需要替换该边
int a = e[i].a, b = e[i].b, w = e[i].w;
// 确保a是深度较大的节点(子节点)
if (deep[a] < deep[b])
{
swap(a, b);
}
if (ids[a] == -1)
{
// 没有可替换的边,无法形成次小生成树
cout << -1 << endl;
}
else
{
// 计算次小生成树权重:最小生成树权重 - 当前边权重 + 替换边权重
cout << ans - w + ids[a] << endl;
}
}
}
return 0;
}
【运行结果】
5 5
1 2 4
2 3 3
3 4 1
2 5 2
3 1 8
14
15
-1
-1
10

浙公网安备 33010602011771号