poj 3723 Kruskal最小生成树

题意:

  • 一堆男女加进一个组,每人的加入费用是10000元。男女之间存在一种亲密度,在加入一个人的时候,ta的加入费是10000减去已经入组的异性中和亲密度最大的。问以某种顺序入组,的最小总费用是多少?

思路:

  • 主要是把这些亲密关系看做无向图,然后要以最小的代价包含所有人,显然是最小生成树。我的做法是,先把有亲密关系的那些人建立无向图,然后求出最小生成树。剩下的人入组费统一10000元。

知识补充:


    • 一个没有圈的连通的无向图就是树,树的边数为顶点数减一。一个没有圈的不连通的无向图就是森林,森林是有很多树组成的森林的边数为定点数减树的数量。树是一个连通无向图边数所能达到的最小情况。
    • 最小生成树的prim算法:首先只有起点为当前点被加入到选定区域中,更新未选定区域中的顶点到当前点的距离为未选定区域顶点到选定区域的距离。然后找出所有未选定区域顶点中据选定区域最近的点,加入选定区域,把该点设为当前点。重复以上步骤,直到所有点都被选中。
    • 最小生成树的Kruskal算法:首先对所有边按照升序排序,然后按照升序依次挑选边加入最小生成树,但是挑选边之前应该先判断这个边是否和前面已经选定的边的顶点形成圈,这时候就要用并查集来高效的判断。如果当前边的两个顶点在同一个连通分量里,那么就会形成圈,不能要这条边,否则可行。
    • 最短路的路径还原办法:这里需要一个prev[max_v]数组,记录当前节点的前驱节点。只需要在最短路算法的没错更新最短路的时候,同时更新对应的前驱节点即可。最后从终点开始还原即可。
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
struct edge{int from, to, cost;}E[50009];
int rank[10008 * 2], par[10008 * 2];

void init(int n) {
    for (int i = 0; i < 10008 * 2; i++) {
        par[i] = i;
        rank[i] = 0;
    }
}

int find(int x) {
    if(par[x] == x) return x;
    else return par[x] = find(par[x]);
}

void unite(int x, int y) {
    x = find(x), y = find (y);
    if (x == y) return;
    if (rank[x] > rank[y]) par[y] = x;
    else {
        par[x] = y;
        if (rank[x] == rank[y]) rank[y]++;
    }
}

bool same(int x, int y) { return find(x) == find(y); }

bool cmp(const edge e1, const edge e2) { return e1.cost < e2.cost;}

int kruskal(int r, int n) {
    int res = 0, count = 1;
    sort(E, E + r, cmp);
    for (int i = 0; i < r; i++) {
        if (!same(E[i].from, E[i].to)) {
            unite(E[i].from, E[i].to);
            res += E[i].cost;
            count++;
        }
    }
    return res + (n - count + 1) * 10000;
}

int main(void) {
    int t;
    scanf("%d", &t);
    while (t--) {
        int n, m, r;
        scanf("%d%d%d", &n, &m, &r);
        n += m;
        init(n);
        for (int i = 0; i < r; i++) {
            scanf("%d%d%d", &E[i].from, &E[i].to, &m);
            E[i].cost = 10000 - m;
            E[i].to += 10000;
        }
        printf("%d\n", kruskal(r, n));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值