其他分享
首页 > 其他分享> > 023(【模板】最小生成树)(最小生成树)

023(【模板】最小生成树)(最小生成树)

作者:互联网

题目:https://www.luogu.com.cn/problem/P3366

题目思路:题目名字就已经说清楚了“最小生成树”

首先,把点(程序中为 m)和边(程序中为 n)输入进去

而后一个 for 循环把边以及这个边的两个端点输入进去

那么该如何存储呢?

首当其冲地,有人想到了用三个一维数组,用一一对应的方式解决

如果依照这个走下去,到后面在使用 kruskal 算法是需要对这些边依照从小到大的顺序进行排列的

可问题就在你把人家存边的数组排了,对应关系不也就不存在了吗?

所以这个“三一维”的方式不可取

所以,此时结构体就能派上用场,排序也只需要多拿出来三行写个 cmp

bool cmp(jardin a,jardin b){//jardin是结构体的名字
	return a.di<b.di;//di是边的权值
}

再把它扔进 kruskal 的函数里,把 ans 加出来,再在主程序里输出

自此,主程序的主体部分成型,到后面出了问题再改就可以

向前翻,跑到核心程序区“kruskal”

首先得想明白,kruskal 是如何运行的

所有的顶点在没有链接之时,都是一个子集,每个顶点单独作为一个子集

而后在所有边中找到一条最小的边,由“MST性质”(详见博客022)可得,这条最小的边如果将两个互不相通的子集联系起来,那么最终的最小生成树肯定有这条边,那么就记录在案,ans+=

BUT! 来做个小实验

把 n 个点拼成一个连通图,最多要多少条边

你会发现你根本就不用把它搞成一个闭环

最多也就是像这样用 n-1 个边凑成一个只开了一个口的闭环

但是,你如果想删除其中一个边,那它会又多出来一个缺口,一共两个缺口把它分成了两个互不相通的子集

你还得要一条边把这俩货连起来,得了,又是 n-1

如法炮制,我们得到了一个普遍规律:“把 n 个点拼在一起,一定要 n-1 条边,且只要 n-1 条边”

这一句话对于顶点集的一个子集也适用

换句话说,最小生成树里不可能存在闭环

如果连起来之后成了一个闭环,或者说在本来就已经连通的一个子集里又要连一条边

那就可以跳过了,这个边并不需要存在

针对这个现象,“并查集”的算法就可以被牵出来溜溜了

首先,来一个F数组,循环令 F[i]=i,是由于上文“所有的顶点在没有链接之时,都是一个子集,每个顶点单独作为一个子集”

如果确定某条边被要了,除了 ans+= 外,还要让 F[边起点]=边终点,表示他们已经合并成了一个子集

再写一个函数,从边的两个点往他们的终点一直跑,什么时候 F[x]=x,也就是没路了的时候,视为结束

如果两个点结束的地方一样,那就证明“他们已经在一个集里”,此时跳过此边

等到什么时候连的边的数量达到了 n-1,就意味着已经成为最小生成树,结束,回到主程序把 ans 打出来

如果到死也没有 n-1,那么给个信号,输出 orz

至此

#include<bits/stdc++.h>
using namespace std;
int m,n,ans=0,f[5001];//f为集 
int x,y,d;
struct jardin{
	int s,e,di;
}q[200001];//结构体存起点,终点与权值 
bool cmp(jardin a,jardin b){//排序 
	return a.di<b.di;
}
int find(int t){//对集进行查找 
	if(f[t]==t){
		return t;
	}
	f[t]=find(f[t]);
	return f[t];
	//死命找祖先 
}
void kruskal(){
	int f1,f2,side=0;//side计数 
	for(int i=1;i<=m;++i){//最开始每个点一个集 
		f[i]=i;
	}
	for(int i=1;i<=n;++i){
		f1=find(q[i].s);
		f2=find(q[i].e);
		//两个点分别找终点 
		if(f1==f2){//找到的终点一样 
			continue;
			//都成一个集了,continue 
		}
		ans+=q[i].di;//加数 
		f[f1]=f2;//合并 
		if(++side==m-1){//如果排出了(点-1) 
			break;//再见 
		}
	}
	if(side!=m-1){//如果到死也没有 
		ans=0;//给信号 
	}
}
int main(){
	scanf("%d%d",&m,&n);
	for(int i=1;i<=n;++i){
		scanf("%d%d%d",&q[i].s,&q[i].e,&q[i].di);
	}
	sort(q+1,q+1+n,cmp);//排序 
	kruskal();
	if(ans!=0){//如果不为零输出ans 
	    printf("%d",ans);
	}
	else{//如果为零,证明上面给了信号,拼不上 
		printf("orz");
	}
	return 0;
} 

标签:一个,最小,生成,子集,023,ans,条边,jardin
来源: https://www.cnblogs.com/a-001/p/16439454.html