题解:洛谷 P2820 局域网
【题目来源】
【题目描述】
现在需要解决回路问题,我们将除去一些连线,使得网络中没有回路,不改变原图节点的连通性,并且被除去网线的 \(\sum f(i,j)\) 最大,请求出这个最大值。
【输入】
第一行两个正整数 \(n,k\)。
接下来的 \(k\) 行每行三个正整数 \(i,j,m\) 表示 \(i,j\) 两台计算机之间有网线联通,通畅程度为 \(m\)。
【输出】
一个正整数, \(\sum f(i,j)\) 的最大值。
【输入样例】
5 5
1 2 8
1 3 1
1 5 3
2 4 5
3 4 2
【输出样例】
8
【核心思想】
-
问题分析:给定一个带权无向图,需要删除一些边使得图中无回路且保持连通性(即形成生成树),同时最大化被删除边的权重之和。等价于:在图中找一棵最小生成树,保留生成树上的边,删除其余边,被删除边的权重和 = 总权重 - 最小生成树权重。
-
算法选择:
- Kruskal 算法:按边权从小到大排序,用并查集维护连通性,每次选择权值最小且不形成环的边
- 补集转化:直接求"删除边权和最大"等价于求"保留边权和最小",即最小生成树
-
关键步骤:
- 读入数据:\(n, m\) 和 \(m\) 条边 \((a, b, c)\),累加总权重
sum += c - Kruskal 算法:
- 边按权重升序排序
- 初始化并查集
p[i] = i - 遍历每条边,若两端点不在同一集合,则合并并累加权重
res - 若最终
cnt < n - 1,图不连通,返回无穷大
- 计算答案:
sum - res
- 读入数据:\(n, m\) 和 \(m\) 条边 \((a, b, c)\),累加总权重
-
时间/空间复杂度:
- 时间复杂度:\(O(m \log m)\),排序 \(O(m \log m)\),并查集操作近似 \(O(1)\)
- 空间复杂度:\(O(n + m)\),并查集和边数组
-
最小生成树补集思想的核心思想:
- 问题等价转化:"删除边权和最大" = "总权重 - 保留边权和最小" = "总权重 - 最小生成树权重"
- 贪心最优性:Kruskal 算法每次选择权值最小的不形成环的边,保证保留边的总权重最小,从而删除边的总权重最大
- 并查集判环:两个端点已在同一集合说明加入该边会形成环,直接跳过
- 连通性检查:生成树需要恰好 \(n-1\) 条边,若不足则原图不连通,无法形成生成树
- 适用于"在保持连通的前提下去除最大权重"的问题,核心在于转化为最小生成树问题
【算法标签】
普及 #生成树
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 105, M = 1005, INF = 1e9;
int n, m, sum; // n: 节点数量,m: 边数量,sum: 所有边的权重总和
int p[N]; // 并查集父节点数组
// 边结构体
struct Edge
{
int a, b, w; // a: 边的一个端点,b: 边的另一个端点,w: 边的权重
bool operator < (const Edge &E)const
{
return w < E.w; // 按照权重从小到大排序
}
}edges[M];
// 并查集查找操作,查找x的祖先节点(路径压缩)
int find(int x)
{
if (p[x] != x) // 如果x不是根节点
p[x] = find(p[x]); // 递归查找并路径压缩
return p[x];
}
// Kruskal算法求最小生成树
int kruskal()
{
sort(edges + 1, edges + m + 1); // 按照边的权重从小到大排序
for (int i = 1; i <= n; i++) // 初始化并查集,每个节点自成一个集合
p[i] = i;
int res = 0, cnt = 0; // res: 最小生成树的权重和,cnt: 已选边数
for (int i = 1; i <= m; i++) // 遍历所有边
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w; // 当前边的两端点和权重
a = find(a), b = find(b); // 查找a和b所在的集合
if (a != b) // 如果a和b不在同一集合中(不会形成环)
{
p[a] = b; // 合并两个集合
res += w; // 累加边权重
cnt++; // 已选边数加1
}
}
if (cnt < n - 1) // 如果选择的边数小于n-1,说明图不连通
return INF;
return res; // 返回最小生成树的权重和
}
int main()
{
cin >> n >> m; // 输入节点数量和边数量
for (int i = 1; i <= m; i++) // 输入每条边的信息
{
int a, b, c;
cin >> a >> b >> c;
if (a > b) // 确保a <= b,便于处理
swap(a, b);
edges[i] = {a, b, c}; // 存储边
sum += c; // 累加所有边的权重
}
int ans = kruskal(); // 计算最小生成树的权重和
cout << sum - ans << endl; // 输出最大生成森林的权重和
return 0;
}
【运行结果】
5 5
1 2 8
1 3 1
1 5 3
2 4 5
3 4 2
8
浙公网安备 33010602011771号