题目地址:
https://www.acwing.com/problem/content/1143/
某个局域网内有 n n n台计算机和 k k k条双向网线,计算机的编号是 1 ∼ n 1∼n 1∼n。由于搭建局域网时工作人员的疏忽,现在局域网内的连接形成了回路,我们知道如果局域网形成回路那么数据将不停的在回路内传输,造成网络卡的现象。
注意:对于某一个连接,虽然它是双向的,但我们不将其当做回路。本题中所描述的回路至少要包含两条不同的连接。两台计算机之间最多只会存在一条连接。不存在一条连接,它所连接的两端是同一台计算机。因为连接计算机的网线本身不同,所以有一些连线不是很畅通,我们用 f ( i , j ) f(i,j) f(i,j)表示 i , j i,j i,j之间连接的畅通程度, f ( i , j ) f(i,j) f(i,j)值越小表示 i , j i,j i,j之间连接越通畅。现在我们需要解决回路问题,我们将除去一些连线,使得网络中没有回路且不影响连通性(即如果之前某两个点是连通的,去完之后也必须是连通的),并且被除去网线的 ∑ f ( i , j ) \sum f(i,j) ∑f(i,j)最大,请求出这个最大值。
输入格式:
第一行两个正整数
n
,
k
n,k
n,k。接下来的
k
k
k行每行三个正整数
i
,
j
,
m
i,j,m
i,j,m表示
i
,
j
i,j
i,j两台计算机之间有网线联通,通畅程度为
m
m
m。
输出格式:
一个正整数,表示被除去网线的
∑
f
(
i
,
j
)
\sum f(i,j)
∑f(i,j)的最大值。
数据范围:
1
≤
n
≤
100
1≤n≤100
1≤n≤100
0
≤
k
≤
200
0≤k≤200
0≤k≤200
1
≤
f
(
i
,
j
)
≤
1000
1≤f(i,j)≤1000
1≤f(i,j)≤1000
问题本质是要求一些边,使得删掉它们之后,剩余的连通块依然不变,并且每个连通块是原连通块的最小生成树。要返回的是删掉的边的总长度。思路是Kruskal算法。在求一个连通图的最小生成树的时候,Kruskal算法的流程是这样的:
1、先将所有的边按照边权从小到大排序;
2、依次尝试加入每个边,如果加边之前两个端点已经连通了,那么就不加这条边,否则要加;
3、遍历完之后就找到了一棵最小生成树(对于连通图来说,最小生成树是一定存在的)。
关于Kruskal算法的正确性。如果某条边 x → y x\to y x→y在Kruskal算法里要加,说明此时 x x x和 y y y各自的连通块是不连通的。如果不加,那么产生的最小生成树里, x x x与 y y y也一定连通,这样连同 x → y x\to y x→y这条边,就产生了一个环。这个环里一定存在某个长度大于等于 x → y x\to y x→y这条边的边(否则的话在加 x → y x\to y x→y这条边之前 x x x和 y y y就已经连通了,Kruskal算法里会略过这条边),将这条边替换为 x → y x\to y x→y这条边,就得到了含 x → y x\to y x→y的最小生成树。说明Kruskal算法每次加的边都可以存在于某个最小生成树中,所以算法正确。这里其实还需要证明算法一定会加恰好 n − 1 n-1 n−1条边。这也是显然的。首先一定不会少加,否则的话由原图连通性,一定存在某条边该加的时候没加;其次也不会多加,当恰好加到 n − 1 n-1 n−1条边之后,所有点已经连通了,后面的边都会略过。
这道题并不会求得一个最小生成树,因为一开始未必所有点都在同一个连通块里。但是这里,按照Kruskal算法的方式来做仍然是正确的,因为在每个连通块单独看来,其实就相当于在这个小连通块上做Kruskal算法。代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110, M = 210;
int n, m;
struct Edge {
int a, b, w;
bool operator<(const Edge &t) const {
return w < t.w;
}
} e[M];
// 这里的p数组是并查集的parent数组
int p[N];
int find(int a) {
if (a != p[a]) p[a] = find(p[a]);
return p[a];
}
int main() {
cin >> n >> m;
// 初始化一下并查集的数组
for (int i = 1; i <= n; i++) p[i] = i;
for (int i = 0; i < m; i++) {
int a, b, w;
cin >> a >> b >> w;
e[i] = {a, b, w};
}
// 按照边权从小到大排序
sort(e, e + m);
int res = 0;
for (int i = 0; i < m; i++) {
int a = find(e[i].a), b = find(e[i].b), w = e[i].w;
// 不连通的边是不能删的,否则可以删,累加边权
if (a != b) p[a] = b;
else res += w;
}
cout << res << endl;
return 0;
}
时间复杂度 O ( m log m ) O(m\log m) O(mlogm), m m m是边的数量,空间 O ( n ) O(n) O(n)。