ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-07-03 11:33:00  阅读:149  来源: 互联网

标签:一个 最小 生成 子集 023 ans 条边 jardin


题目: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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有