一、题意解析
本题要求被除去网线的通畅程度之和最大,则要求留下来的网线通畅程度最小,也就是求图的最小生成树,由于原图不一定是连通图,所以要求的实际上是原图的最小生成森林,即若干个生成树的集合。可以用\(Prim\)或\(Kruskal\)算法求解,时间复杂度为\(O(mlogm)\)。
二、Prim算法
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 110;
int w[N][N];
int dist[N];
bool st[N];
int n, m, sum;
int prim() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
int res = 0;
for (int i = 1; i <= n; i++) {
int t = -1;
for (int j = 1; j <= n; j++)
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
st[t] = true;
if (dist[t] != INF) res += dist[t]; //累加最小生成树的边权
for (int j = 1; j <= n; j++)
dist[j] = min(dist[j], w[t][j]);
}
return res;
}
int main() {
cin >> n >> m;
memset(w, 0x3f, sizeof w);
for (int i = 1; i <= m; i++) {
int a, b, c;
cin >> a >> b >> c;
w[a][b] = w[b][a] = min(w[a][b], c);
sum += c;
}
cout << sum - prim() << endl;
return 0;
}
三、Kruskal算法
简单回忆下\(kruskal\)算法。并查集中用生成树的根节点作为这个集合的表示,如果两个节点所在集合的标识相同,就处在同一个集合中。用find(int x)
函数求x
所在集合的标识,初始情况下各个点互不联通,所以某个节点在生成树中的父节点fa[i] = i
,也就是其本身,在find
函数中,一旦x == fa[x]
就说明x
是树的根节点,直接返回自身的值即可,否则继续对x
的父节点fa[x]
调用find
函数,知道找到生成树的根节点。
\(kruskal\)算法的过程是:首先按照边权对所有边从小到大排序,然后自小到大遍历边的集合,如果某条边的两个顶点\(a\)和\(b\)还不在一个集合中,就将\(a\)的父节点设置为\(b\),相当于合并了这两个集合。总的代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = 210;
int n, m, fa[N];
//结构体
struct Edge {
int a, b, w;
} e[M];
//重载小于号,让结构体按边权进行排序,sort后由小到大排序
bool operator<(const Edge &a, const Edge &b) {
return a.w < b.w;
}
//并查集
int find(int x) {
if (fa[x] != x) fa[x] = find(fa[x]); //路径压缩
return fa[x];
}
int main() {
cin >> n >> m;
int a, b, w;
//并查集初始化
for (int i = 1; i <= n; i++) fa[i] = i;
// Kruskal算法直接记录结构体
for (int i = 0; i < m; i++) {
cin >> a >> b >> w;
e[i] = {a, b, w};
}
sort(e, e + m); //不要忘记e数组的长度是边的数量
int res = 0;
//枚举每条边
for (int i = 0; i < m; i++) {
a = find(e[i].a), b = find(e[i].b), w = e[i].w;
if (a != b)
fa[a] = b;
else
res += w; //去掉的边权
}
cout << res << endl;
return 0;
}