学习Kruskal并用其解决洛谷P1195口袋的天空

先上题:P1195口袋的天空

相比于Prim算法,Kruskal算法更好理解一些。

首先介绍这两个算法思想的区别:

Prim算法是从点出发,不断查找距离当前生成树最近的点并将其加入。所有点都加入生成树后,得到的就是最小生成树。

Kruskal算法是从边出发,每次取出剩余边中的最短边,查看边的两个端点是否在一个生成树内,如果不在,则将其连接在同一个生成树内。所有边遍历后,最后得到就是最小生成树。

上面就是Kruskal的算法思路,看起来非常简单,但是这里有几个需要解决的问题;

1.枚举所有边,每次枚举的都是剩余边中的最短边。

2.每次枚举边要查看两个端点是否在一个生成树内,如果不是则连接在一起。

第一个问题很好解决,将边按长度从小到大排序,然后从前向后遍历即可。

那么怎么能很快地解决第二个问题呢?我们要用到并查集这个数据结构。

我们使用带路经压缩的并查集,可以很快地完成查询以及合并操作。

 

这里简单得讲一下并查集。并查集主要是三个操作:1.找根 2.判断是否连接 3.合并

1.找根:我们使用一个数组来记录每个节点的根,初始情况下每个节点的根都是其自身。那么找根的操作就很显而易见。比如我们要找x的根,那么我们就一直比较x与其父亲的根(father[x]),因为跟的根是自身,因此当遍历到x=father[x]时,我们就找到了根。

2.判断是否连接:如果我们要判断x,y是否连接在一个集合内,那我们就可以去找x与y的根,如果根相同,则在一个集合内,反之则不在。

3.合并:如果我们要合并x,y,要明确的是,虽然我们合并的是x,y连个节点,但实际上合并的是x,y所在的连个集合。我们首先判断x,y是否已连接,如果没有,则分别找到x,y的根,将x的根连接在y上(反之也可)。

具体代码在一会的题解代码中有

 

对于本题,仅仅使用Kruskal还不够,因为题中要求连成k个最小生成树的最小代价。

我的思路是,首先连成一棵最大的最小生成树,连接的过程中记录每次连接的代价cost,然后判断剩余点数量+1是否等于k,如果相等,则此时的cost就是答案,如果大于k,则无论如何都无法连成k棵生成树。如果小于k,那么我们可以采用从之前连成的生成树上“剪枝”的方式(很显而易见,每剪去一条边,原来的生成树就变成了两棵生成树),每次减去与当前存在的生成树相连的最大边,cost同时减去这个边的开销(不再需要连接这条边),直到剩余的生成树数量等于k,此时的cost就是答案。

 

AC代码:

#include 
#include 
#include 
#include 
using namespace std;

int n, m, k;
int tree[1005];
int size[1005];

struct node {
    int a;
    int b;
    int len;
    bool use;
    node() {}
    node(int a_, int b_, int c_) :a(a_), b(b_), len(c_), use(false) {}
};
vector buf;
long long int cost = 0;

bool cmp(node a, node b) {
    return a.lensize[root_b]){
        tree[root_b] = root_a;
        size[root_a]+=size[root_b];
    }
    else {
        tree[root_a] = root_b;
        size[root_b]+=size[root_a];
    }
}
bool connect(int a, int b) {
    int root_a = root(a);
    int root_b = root(b);
    if (root_a == root_b)return true;
    else return false;
}

void kruskal() {
    vector::iterator it;
    for (it = buf.begin(); it != buf.end(); it++) {
        int a = it->a;
        int b = it->b;
        if (connect(a, b))continue;
        else {
            Union(a, b);
            cost += it->len;
            it->use = true;
        }
    }
}

bool deal() {
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        if (tree[i] == i)cnt++;
    }
    if (cnt>k)return false;
    else if (cnt == k)return true;
    else {
        int gap = k-cnt;
        vector temp;
        vector::iterator it;
        for (it = buf.begin(); it != buf.end(); it++) {
            if (it->use) {
                temp.push_back(*it);
            }
        }
        for (it = temp.end() - 1; it != temp.end() - 1 - gap; --it) {
            cost -= it->len;
        }
        return true;
    }
}


int main() {
    cin >> n >> m >> k;
    int x, y, l;
    for (int i = 0; i> x >> y >> l;
        buf.push_back({ x,y,l });
    }
    sort(buf.begin(), buf.end(), cmp);
    init();
    kruskal();
    if (deal())cout << cost;
    else cout << "No Answer";
    return 0;
}

 

你可能感兴趣的:(算法学习)