最小生成树算法
Kruskal算法
给定一张无向图,若用这张图的全部节点构成一棵树,且这棵树所有边的权值之和最小,则称这棵树为最小生成树。
所以很显然我们只需找出条边,让这些边使这张图不构成环的前提下,边权尽可能小,这就是Kruskal算法。
算法流程:我们把边权按照从小到大排序,然后按顺序把他们接到图上,用并查集维护看是否构成环,直到找出条边。
代码如下:
#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;
}
口袋的天空:
题目要求我们连成棵树,同时花费最小,很显然这题要用最小生成树来解了。
可是最小生成树只能解决生成一棵树,棵树该如何解决?
别急,先让我们分析一下:
个节点若是连成一棵树,其边的数量为。
这棵树把它拆成个部分,这个部分之间的边的数量为。
所以把个节点连成棵树需要的边的数量为。
而且用条边连成的图也肯定是棵树(如果不是棵树,假设为棵树,那所需的边则为而不是了)。
这时候就可以用我们熟悉的最小生成树算法,连满条边就行啦!
代码:
#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;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!