图论 最小生成树
Posted keliven
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图论 最小生成树相关的知识,希望对你有一定的参考价值。
图论 最小生成树
最小生成树 (MST)
? 最小生成树是一副连通加权无向图中一棵权值最小的生成树.
? 在一个有n个点的图中, 在所有边中选择n - 1条边, 连接所有n个点, 使这n - 1条边的权值和是所有方案中最小的.
? 最小生成树在算法竞赛中很少单独考察, 常与图论及其他树上算法结合, 因此对于最小生成树的掌握是十分必要的.
算法
Kruskal (克鲁斯卡尔)
基本思路
? 将所有点视为单独集合, 并将所有边按照边权从小到大排序, 然后顺序枚举每一条边. 如果这条边连接两个不同集合, 就将此边加入最小生成树中, 并合并这两个不同集合, 直至取了n - 1边.
? 对于不同点的关系,使用并查集维护.
代码
#include<bits/stdc++.h>
using namespace std;
struct Edge
int dis, from, to;
edge[1000001];
int ans;
int cnt;
int num_edge;
int n, m;//点数, 边数
int bin[1000001];
void add(int from, int to, int dis)
edge[++cnt].dis = dis;
edge[cnt].to = to;
edge[cnt].from = from;
int find(int x)
if(bin[x] == x) return x;
return bin[x] = find(bin[x]);
//查询
void uni(int x, int y)
bin[find(x)] = find(y);
//合并
bool check(int x, int y)
if(find(x) == find(y)) return true;
return false;
//判断
bool cmp(Edge x, Edge y)
return x.dis < y.dis;
void Kruskal()
for(int i = 1;i <= n;i++)
bin[i] = i;
sort(edge + 1, edge + 1 + m, cmp);
//未建图情况下构建最小生成树
/*for(int i = 1;i <= m;i++)
if(! check(edge[i].from, edge[i].to))
add(edge[i].from, edge[i].to, edge[i].dis);
add(edge[i].to, edge[i].from, edge[i].dis);
uni(edge[i].from, edge[i].to);
*/
for(int i = 1;i <= m;i++)
if(check(edge[i].to, edge[i].from))
continue;
ans += edge[i].dis;
uni(edge[i].from, edge[i].to);
if(++num_edge == n - 1)//树中边数 = 点数 - 1;
break;
int main()
scanf("%d %d", &n, &m);
for(int i = 1;i <= m;i++)
int x, y, z;
scanf("%d %d %d", &x, &y, &z);
add(x, y, z);
Kruskal();
printf("%d", ans);
return 0;
? Kruskal使用贪心思想, 每次选取的边可以连接两不同集合且代价最小, 因此最后一定可以将n个点合并为一个集合并使总代价最小.
? 时间复杂度:O(m*logm), m为边数.
? 另外还有Prim算法也常用于求最小生成树,时间复杂度为O(n^2), 与Kruskal在实际使用时差距不大, 也可另作学习,
例题
luogu p1111 修复公路
题目背景
AAA地区在地震过后,连接所有村庄的公路都造成了损坏而无法通车。政府派人修复这些公路。
题目描述
给出A地区的村庄数n和公路数m,公路是双向的。并告诉你每条公路的连着哪两个村庄,并告诉你什么时候能修完这条公路。问最早什么时候任意两个村庄能够通车,即最早什么时候任意两条村庄都存在至少一条修复完成的道路(可以由多条公路连成一条道路)
输入格式
第1行两个正整数n, m
下面m行,每行3个正整数x, y, t,告诉你这条公路连着x, y两个村庄,在时间t时能修复完成这条公路。
输出格式
如果全部公路修复完毕仍然存在两个村庄无法通车,则输出?1,否则输出最早什么时候任意两个村庄能够通车。
思路
? 接近裸题, 直接根据题意求最小生成树, 唯一不同是因为题中需判断是否可以全连通, 所以在算法执行过程中直接统计全部边数, 最后根据边数进行判断.
代码
#include<bits/stdc++.h>
using namespace std;
int n, m;
int cnt;
int num_edge;
int bin[1000001];
int ans;
struct Edge
int from, to, dis;
edge[1000001];
void add(int from, int to, int dis)
edge[++cnt].from = from;
edge[cnt].to = to;
edge[cnt].dis = dis;
int find(int x)
if(bin[x] == x) return x;
return bin[x] = find(bin[x]);
void uni(int x, int y)
bin[find(x)] = find(y);
bool check(int x, int y)
if(find(x) == find(y)) return true;
return false;
bool cmp(Edge a, Edge b)
return a.dis < b.dis;
void Kruskal()
sort(edge + 1, edge + 1 + m, cmp);
for(int i = 1;i <= n;i++)
bin[i] = i;
for(int i = 1;i <= m;i++)
if(check(edge[i].from, edge[i].to)) continue;
ans = max(ans, edge[i].dis);
uni(edge[i].from, edge[i].to);
//if(++num_edge == n - 1) break;
num_edge++;
int main()
scanf("%d %d", &n, &m);
for(int i = 1;i <= m;i++)
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
add(u, v, w);
Kruskal();
if(num_edge >= n - 1)
printf("%d", ans);
return 0;
printf("-1");
return 0;
luogu p1194 买礼物
题目描述
又到了一年一度的明明生日了,明明想要买B样东西,巧的是,这B样东西价格都是A元。
但是,商店老板说最近有促销活动,也就是:
如果你买了第I样东西,再买第J样,那么就可以只花KI,J元,更巧的是, KI,J竟然等于KJ,I。
现在明明想知道,他最少要花多少钱。
输入格式
第一行两个整数,A,B
接下来B行,每行B个数,第I行第J个为KI,J。
我们保证KI,J=KJ,I, 并且KI,I=0。
特别的,如果KI,J = 0,那么表示这两样东西之间不会导致优惠。
输出格式
一个整数,为最小要花的钱数。
思路
? 直接最小生成树, 但因为两件物品先买哪一件优惠相同, 因此对于输入格式, 需判断i与j的大小关系以及w是否为0, 在进行Kruska前先将买第一件物品时由0到每一件物品建边, 代价为a.
代码
#include<bits/stdc++.h>
using namespace std;
int a, b;
int cnt;
int ans;
struct Edge
int from, to;
int dis;
edge[1000001];
int bin[1000001];
int num_edge;
void add(int from, int to, int dis)
edge[++cnt].from = from;
edge[cnt].to = to;
edge[cnt].dis = dis;
int find(int x)
if(x == bin[x]) return x;
return bin[x] = find(bin[x]);
void uni(int x, int y)
bin[find(x)] = find(y);
bool check(int x, int y)
if(find(x) == find(y)) return true;
return false;
bool cmp(Edge a, Edge b)
return a.dis < b.dis;
void Kruskal()
for(int i = 1;i <= b;i++)
bin[i] = i;
sort(edge + 1, edge + 1 + cnt, cmp);
for(int i = 1;i <= cnt;i++)
if(check(edge[i].from, edge[i].to)) continue;
ans += edge[i].dis;
uni(edge[i].from, edge[i].to);
if(++num_edge == b) break;
int main()
scanf("%d %d", &a, &b);
for(int i = 1;i <= b;i++)
for(int j = 1;j <= b;j++)
int w;
scanf("%d", &w);
if(i < j && w)
add(i, j, w);
for(int i = 1;i <= b;i++) add(0, i, a);
Kruskal();
printf("%d", ans);
return 0;
END
以上是关于图论 最小生成树的主要内容,如果未能解决你的问题,请参考以下文章