先上题: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 <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
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<node> buf;
long long int cost = 0;
bool cmp(node a, node b) {
return a.len<b.len;
}
void init() {
for (int i = 1; i <= n; i++){
tree[i] = i;
size[i]=1;
}
}
int root(int x) {
while (x != tree[x]) {
x = tree[x];
tree[x] = tree[tree[x]];//path compression
}
return x;
}
void Union(int a, int b) {
int root_a = root(a);
int root_b = root(b);
if (root_a == root_b)return;
if(size[root_a]>size[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<node>::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<node> temp;
vector<node>::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<m; i++) {
cin >> 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;
}