最小生成树
无向图:图的边没有标明方向
无向连通图:满足任意两点均可经过任意条边相互到达的无向图
树:有 \(n\) 个点和 \(n-1\) 条边的无向连通图
无向连通图的生成树:选定图上的一些边,将所有点连成树的形状
最小生成树:假定边带有权值,在所有生成树中,选定边的边权之和最小的生成树
最小生成树一定包含权值最小的边
证明考虑反证法
如果不包含的话,一定能找到一条将这条边的两个点割成两个部分的边
切掉这条边加入权值最小的边,肯定会让权值更小,与“最小”矛盾
推广:
如果已经确认了 \(k\) 条边,那么剩下的 \(n-1-k\) 条边一定满足:
1.连接了两个之前不连通的节点 2.权值最小
Prim
类似于Dijsktra算法
考虑每次加入一个点,假设已经确定最小生成树的点集为 \(V\) 剩下的部分为 \(S\)
贪心地选取满足 \(min{w_{x,y}},x\in S,y\in V\) 取到最小值的点 \(x\)
把 \(x\) 加入 \(V\) 连带这条边算进总权值里,然后用 \(x\) 更新一下集合 \(S\)
类似的,选边权最小值的暴力是 \(O(n^2)\) 的
这个过程同样可以用堆维护,时间复杂度 \(O(m\log m)\)
不过同复杂度下,我们有另外一个算法,相对而言更加简洁
Prim
#include <bits/stdc++.h>
using namespace std;
const int p=222;
int m[p][p],dis[p];
int n;
int ans;
void in(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&m[i][j]);
}
}
}
void prim(int x){
ans=0;
for(int i=1;i<=n;i++){
dis[i]=m[x][i];
}
dis[x]=0;
for(int i=2;i<=n;i++){
int min=0x7f7f7f7f,k;
for(int j=1;j<=n;j++){
if(dis[j]<min&&dis[j]!=0){
min=dis[j];
k=j;
}
}
ans+=dis[k];
dis[k]=0;
for(int j=1;j<=n;j++){
if(dis[j]>m[k][j]){
dis[j]=m[k][j];
}
}
}
}
void out(){
printf("%d",ans);
}
int main(){
in();
prim(2);
out();
return 0;
}
Kruscal
采用并查集维护最小生成树
贪心地选取边权尽量小的边,如果这两个点没有被连通就选上,同时维护新的连通性
正确性显然,时间复杂度为 \(O(m\log m)\)
Kruskal
#include <bits/stdc++.h>
using namespace std;
const int p=100001;
struct edge{
int s;
int t;
int v;
}e[p];
int f[p],n,m;
int ans,t;
int find(int x){
if(f[x]!=x){
f[x]=find(f[x]);
}
return f[x];
}
bool cmp(const edge &a,const edge &b){
return a.v<b.v;
}
void in(){
for(int i=1;i<=m;i++){
scanf("%d%d%d",&e[i].s,&e[i].t,&e[i].v);
}
sort(e+1,e+m+1,cmp);
}
void renew(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
f[i]=i;
}
}
void work(){
for(int i=1;i<=m;i++){
int x=e[i].s,y=e[i].t;
int fx=find(x),fy=find(y);
if(fx!=fy){
f[fx]=fy;
ans=e[i].v;
t++;
}
if(t==n-1){
break;
}
}
}
void out(){
printf("%d %d\n",t,ans);
}
int main(){
renew();
in();
work();
out();
return 0;
}
例题
P1195
维护一个 \(K\) 个连通块的森林
考虑克鲁斯卡尔的过程,本质上是由森林合并成树的过程
每合并两个连通块就会减少一个连通块
初始有 \(n\) 个连通块,维护一下当前连通块的总数即可
P1194
新建一个点 \(S\),从 \(1\) 到 \(n\) 连一条 \((S,i)\) 边权为 \(A\) 的边
正常做最小生成树即可
P1396
看到最大值最小,大家可能会想到二分答案
需要二分吗?
从小到大加边直到两点连通就可以了
为什么?
因为任何小于边权 \(w\) 的路径都不可能使 \(s\) 到达 \(t\)
而且走别的路径只会让最大值更大
也就是最大值是单调不降的
CF1245D
前面建虚点的套路重了
注意到 \(n\leq 2000\) 并且是完全图
考虑边数的量级
用 Prim
P2700
答案要求最小化删边,所有边权已经知道
那么如果删干净之后,知道最大化加边即可求出答案
最大生成树同样是类似的过程
注意并查集维护的时候需要标记该集合是否包含特殊节点

浙公网安备 33010602011771号