AcWing 1141 局域网


一、题意解析

本题要求被除去网线的通畅程度之和最大,则要求留下来的网线通畅程度最小,也就是求图的最小生成树,由于原图不一定是连通图,所以要求的实际上是原图的最小生成森林,即若干个生成树的集合。可以用\(Prim\)\(Kruskal\)算法求解,时间复杂度为\(O(mlogm)\)

二、Prim算法

#include 
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 
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;
}