最小生成树算法

Kruskal算法

给定一张无向图,若用这张图的全部节点构成一棵树,且这棵树所有边的权值之和最小,则称这棵树为最小生成树。

所以很显然我们只需找出n1条边,让这些边使这张图不构成环的前提下,边权尽可能小,这就是Kruskal算法。

算法流程:我们把边权按照从小到大排序,然后按顺序把他们接到图上,用并查集维护看是否构成环,直到找出n1条边。

代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int fa[100001], n, m, ans;
struct Edge {
    int x, y, z;
} edge[500001];
bool operator < (Edge a, Edge b) {
    return a.z < b.z;
}
int get (int x) {
    if (fa[x] == x) return x;
    else return fa[x] = get(fa[x]);
} //并查集
void Kruskal () {
	for (int i = 1; i <= m; i ++) {
        int x = get(edge[i].x);
        int y = get(edge[i].y);
        if (x == y) continue; //构成环则跳过这条边
        fa[x] = y;
        ans += edge[i].z;
    }
}
int main () {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i ++)
        scanf("%d%d%d", &edge[i].x, &edge[i].y, &edge[i].z);
    sort(edge + 1, edge + m + 1);
    for (int i = 1; i <= n; i ++)
        fa[i] = i;
    Kruskal();
    printf("%d", ans);
    return 0;
}

是不是很简单,那么去水一道模板题

这道模板题还要判断是否能够构成最小生成树,那么可以用v数组记录用到过的节点,最后再判断一下是不是所有节点都被用到就行了(其实这题根本不用判断也能过= =)

Prim算法

Prim算法与Dijkstra算法类似,具体见代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
int n, m, ans, dis[5001], vis[5001], edge[5001][5001];
void Prim () {
	memset(dis, 63, sizeof(dis));
	dis[1] = 0;
	for (int i = 1; i <= n; i ++) {
		int x = 0;
		for (int j = 1; j <= n; j ++)
			if (!vis[j] && (x == 0 || dis[x] > dis[j]))
				x = j;
		vis[x] = 1;
		for (int y = 1; y <= n; y ++)
			if (!vis[y])
				dis[y] = min(dis[y], edge[x][y]); 
                //这边我们与edge[x][y]比较是因为我们现在在意的是边的权值而不是y到源点的距离
	}
}
int main () {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++)
    	for (int j = 1; j <= n; j ++)
    		edge[i][j] = INF;
    for (int i = 1; i <= m; i ++) {
    	int x, y, z;
    	scanf("%d%d%d", &x, &y, &z);
    	edge[x][y] = edge[y][x] = min(edge[x][y], z);
	}
    Prim();
    for (int i = 1; i <= n; i ++)
    	ans += dis[i];
    printf("%d", ans);
    return 0;
}

所以试着用Prim算法A掉上面的模板题吧。

试题

T1:战争

T2:口袋的天空

题解

战争:

题目说到只有当塔之间的墙围成一圈的时候,才能产生能量层,我们的目的就是破坏能量层。

围成一圈?那不就是个环吗?

所以很显然,只要让破坏后的墙之间无法连成环就可以了。

这是不是很最小生成树有点类似?

但是同时,要尽可能的减小损失,那么意味着破坏能量层后,这个图的边权是尽可能大的。

这不就是最大生成树吗!

所以只需要把最小生成树的代码稍微改改,使每次取的都是尽可能大的边就可以了。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
const int M = 10001;
int m, ans, attack[M], fa[M], head[2 * M], ver[2 * M], next[2 * M], tot, cnt, tmp, vis[M];
struct Edge {
	int u, v, w;
}edge[M * M];
bool cmp (Edge a, Edge b) {
	return a.w > b.w;
}
void add (int x, int y) {
	ver[++ tot] = y;
	next[tot] = head[x];
	head[x] = tot;
}
int get (int x) {
	if (fa[x] == x) return x;
	else return fa[x] = get(fa[x]);
}
int main () {
	scanf("%d", &m);
	for (int i = 1; i <= m; i ++) {
		int u, v, w, y;
		scanf("%d%d%d", &u, &v, &w);
		attack[u] = v;
		for (int j = 1; j <= w; j ++) {
			scanf("%d", &y);
			add(u, y);
		}
	}
	for (int i = 0; i < m; i ++) {
		vis[i] = 1;
		fa[i] = i;
		for (int j = head[i]; j; j = next[j]) {
			if (vis[ver[j]])
				continue;
			tmp += attack[i] + attack[ver[j]];
			edge[++ cnt] = (Edge){i, ver[j], attack[i] + attack[ver[j]]};
		}
	}
	sort(edge + 1, edge + cnt + 1, cmp);
	for (int i = 1; i <= cnt; i ++) {
		int x = get(edge[i].u);
		int y = get(edge[i].v);
		if (x == y)
			continue;
		fa[x] = y;
		ans += edge[i].w;
	}
	printf("%d", tmp - ans);
	return 0;
}

口袋的天空:

题目要求我们连成k棵树,同时花费最小,很显然这题要用最小生成树来解了。

可是最小生成树只能解决生成一棵树,k棵树该如何解决?

别急,先让我们分析一下:

n个节点若是连成一棵树,其边的数量为n1

这棵树把它拆成k个部分,这k个部分之间的边的数量为k1

所以把n个节点连成k棵树需要的边的数量为(n1)(k1)=nk

而且用nk条边连成的图也肯定是k棵树(如果不是k棵树,假设为x棵树,那所需的边则为nx而不是nk了)。

这时候就可以用我们熟悉的最小生成树算法,连满nk条边就行啦!

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int fa[1010], n, m, k, ans, tot;
struct Edge {
    int x, y, z;
} edge[10010];
bool operator < (Edge a, Edge b) {
    return a.z < b.z;
}
int get (int x) {
    if (fa[x] == x) return x;
    else return fa[x] = get(fa[x]);
}
void Kruskal () {
    for (int i = 1; i <= m; i ++) {
        int x = get(edge[i].x);
        int y = get(edge[i].y);
        if (x == y) continue;
        fa[x] = y;
        ans += edge[i].z;
		tot ++;
        if (tot == n - k)
            break;
    }
}
int main () {
    scanf("%d%d%d", &n, &m, &k);
    if (m < n - k) {
        printf("No Answer");
        return 0;
    }
    if (n == k) {
    	printf("0");
    	return 0;
	}
    for (int i = 1; i <= m; i ++)
        scanf("%d%d%d", &edge[i].x, &edge[i].y, &edge[i].z);
    sort(edge + 1, edge + m + 1);
    for (int i = 1; i <= n; i ++)
        fa[i] = i;
    Kruskal();
    printf("%d", ans);
    return 0;
}
posted @   duoluoluo  阅读(84)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示