最小生成树学习笔记

这篇具有很好参考价值的文章主要介绍了最小生成树学习笔记。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

定义

最小生成树是指给定一个带权连通图 G,如果里面有一个子图 G' 中的边权和加起来最小并且使得所有的点都能两两相通。

性质

从上述的定义可以看出,最小生成树有以下性质:

  1. 如果图 G 中有 n 个点的话,G'中的边数为 n-1 且 G' 中不含有环。

  2. 最小生成树可能是一个,也可能是多个。

还有一些复杂的性质感兴趣的可以自行百度。

kruskal 算法

前置知识:并查集。

kruskal 算法的基本思想就是贪心,该算法的流程也很简单。

首先我们需要将所有的边进行排序,然后枚举每一条边,如果当前的边链接的两个点早已经间接或直接相连,我们就直接跳过,不连这条边,如果连满了 n-1 条边就直接退出循环,然后我们所选的边就构成了一棵最小生成树。

在判断当前边的两点是否已经相连的办法可以用并查集来判断,也是比较方便的做法。

根据其定义可以想到,我们要贪心的话,就得先加入边权小的边,假设只有两个点,那他的最小生成树就是两点之间边权最小的边构成,当到了三个点之后,就是从前面已经链接的所有边中覆盖的点与第三个点相连的边中挑一条边权最小的边来连,因为如果要是想要把第三个点连进去,就必须要与之前的联通图中的任意一点连一条边,经过这样不断的扩大,最终会以最小的代价连进所有点。在算法流程中,其当前连接的边是必定有一个点是在联通图中,必有一个点未连进联通图中,而当前边又是剩下的边中边权最小的,所以一定是原图中一棵最小生成树的边。

以上不理解也没关系反正是我胡扯的背板子就好了

最小生成树学习笔记

P3366 【模板】最小生成树

code:文章来源地址https://www.toymoban.com/news/detail-431509.html

#include<bits/stdc++.h>
#define int long long
#define N 1000100
using namespace std;
int n,m,f[N],num,ans;
struct sb{int x,y,w;}e[N];
inline int cmp(sb a,sb b){return a.w<b.w;}
inline int fid(int x){if(x==f[x])return x;else return f[x]=fid(f[x]);}
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)f[i]=i;
	for(int i=1;i<=m;i++)cin>>e[i].x>>e[i].y>>e[i].w;
	sort(e+1,e+m+1,cmp);
	for(int i=1;i<=m;i++)
	{
		int xx=fid(e[i].x);
		int yy=fid(e[i].y);
		if(xx==yy)continue;
		f[xx]=yy;
		ans+=e[i].w;
		num++;
		if(num==n-1)break;
	}
	if(num==n-1)cout<<ans<<endl;
	else cout<<"orz"<<endl;
	return 0;
}

1487:【例 2】北极通讯网络

题目说的有点绕,但仔细读完就会发现意思是让你找一棵最小生成树,其中有 k 个点是不必连入的,也就是等同于让你连 n-k 条边。

code:

#include<bits/stdc++.h>
#define int long long
#define N 1000100
using namespace std;
double ans;
int n,k,fa[N],num,m;
struct sb{int x,y;}e1[N];
struct SB{int u,v;double w;}e[N<<5];
inline int cmp(SB a,SB b){return a.w<b.w;}
inline int fid(int x){if(fa[x]==x)return x;return fa[x]=fid(fa[x]);}
inline double js(sb u,sb v){return sqrt(pow(u.x-v.x,2)+pow(u.y-v.y,2));}
signed main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)fa[i]=i,cin>>e1[i].x>>e1[i].y;
	for(int i=1;i<=n;i++)
	  for(int j=i;j<=n;j++)
	    e[++m].u=i,e[m].v=j,e[m].w=js(e1[i],e1[j]);
	sort(e+1,e+m+1,cmp); 
	for(int i=1;i<=m;i++)
	{
		int xx=fid(e[i].u);
		int yy=fid(e[i].v);
		if(xx==yy)continue;
		fa[xx]=yy;num++;
		ans=max(e[i].w,ans);
		if(num==n-k)break;
	}
	printf("%.2lf\n",ans);
	return 0;
}

1488:新的开始

题目中如果要是是只有一个固定的发电站的话,那就是纯板子了,但是这个也不难,我们考虑一下如果只连边的话,这个电网里面是没有电的,所以我们需要开一个点 n+1 来存放发电站,然后把题目中的在当前点建发电站转化成当前点直接和发电站连边,也就是从 n+1 向每一个点连一条边边权为 v,这样就相当于连 n 条边。

code:

#include<bits/stdc++.h>
#define int long long
#define N 1000100
using namespace std;
int n,m,fa[N],mp[500][500],val[N],num,ans;
struct sb{int u,v,w;}e[N];
inline int cmp(sb a,sb b){return a.w<b.w;}
inline int fid(int x){if(fa[x]==x)return x;return fa[x]=fid(fa[x]);}
signed main()
{
	cin>>n;
	for(int i=1;i<=n+1;i++)fa[i]=i;
	for(int i=1;i<=n;i++)cin>>val[i],e[++m].u=n+1,e[m].v=i,e[m].w=val[i];
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=n;j++)
	    cin>>mp[i][j],e[++m].u=i,e[m].v=j,e[m].w=mp[i][j];
	sort(e+1,e+m+1,cmp);
	for(int i=1;i<=m;i++)
	{
		int xx=fid(e[i].u);
		int yy=fid(e[i].v);
		if(xx==yy)continue;
		fa[xx]=yy;
		num++;
		ans+=e[i].w;
		if(num==n)break ;
	}
	cout<<ans<<endl;
	return 0;
}

prim 算法

由于 prim 本人不是很熟练此部分参考 https://www.cnblogs.com/bcoier/p/10293059.html
我看到里面有 kruskal 的流程图所以也粘过来了

这个算法的流程本质也是贪心,首先需要我们选定任意一个节点作为根节点,然后往下开始扩展,在每一次扩展的过程中都选用待选边(链接 u,v)中权值最小的边进行扩展,然后将除此边外与 v 相连的所有边都放入待选边,如果此时加入的边与 v 相连的点中有的点实际上是已经有待定边相连了,就更新最短路,也就是取个 min,让选取的边权和尽可能的小。

可以看看上面博客的图:

最小生成树学习笔记

P3366 【模板】最小生成树

没错就是上面那道,结合注释和图理解一下 prim 的流程。

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define int long long
#define N 1000100
using namespace std;
int n,m,head[N],dis[N],cnt,num,now=1,ans,vis[N];
//dis表示从已经在连通块里的点到当前点的最短距离,vis标记是否在连通图内,now是当前节点 
struct sb{int u,v,w,next;}e[N];
inline void add(int u,int v,int w)//链式前向星存图 
{
	e[++cnt].u=u;
	e[cnt].v=v;
	e[cnt].w=w;
	e[cnt].next=head[u];
	head[u]=cnt;
}
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);
		add(v,u,w);
	}
	for(int i=2;i<=n;i++)
	  dis[i]=INF;//dis一开始都设极大值,后面要取min 
	for(int i=head[1];i;i=e[i].next)//枚举与起点相连的边 
	  dis[e[i].v]=min(dis[e[i].v],e[i].w);//更新dis,取min 
	while(1)//只要没完成就一直找 
	{
		int minn=INF;//存放当前的最小的没在连通图里的dis的值 
		vis[now]=1;//当前点加入连通图,now存下标 
		for(int i=1;i<=n;i++)//枚举n个点 
		  if(!vis[i]&&minn>dis[i])//找最小的不在连通图的dis 
		    now=i,minn=dis[i];//存值 
		ans+=minn;//加入当前边 
		for(int i=head[now];i;i=e[i].next)//枚举每一个与之相连的边 
		  if(dis[e[i].v]>e[i].w&&!vis[e[i].v])//更新dis,除在连通图的点之外 
		    dis[e[i].v]=e[i].w;
		num++;//加边数+1 
		if(num==n-1||minn==INF)break;//如果到n-1条边了或者minn没值了 
	}
	if(num==n-1)cout<<ans<<endl;//输出答案 
	else cout<<"orz"<<endl;
	return 0;
}

由于 prim 不如 kruskal 好写所以我只写了一个模板。

关于他俩谁快的问题,稠密图 prim 快一点,稀疏图 kruskal 快一点。

严格次小生成树

本来这个是不打算写的了,但是想了想还是来一发吧。

屠龙宝刀点击就送

看完题目发现要求的不是最小生成树,而是一个新名词:严格次小生成树,也就是边权和严格第二小的生成树。

首先题目说的是严格次小,也就是说,如果换了一条边,但是边权和没变,这种是不算严格次小生成树的。

\(mst\) 表示当前最小生成树的边权和, \(Mst\) 表示严格次小生成树的边权和,\(maxUV\) 表示 u 到 v 的路径上的最大边权,\(MaxUV\) 表示从 u 到 v 的路径上的次大的边权。

所以我们得到两种情况:

  1. 当前要加入的边 \(e\ne maxUV\),那么得到一个 \(Mst\) 的侯选值 \(mst-maxUV+e\)

  2. 当前要加入的边 \(e = maxUV\),那么得到一个 \(Mst\) 的侯选值 \(mst-MaxUV+e\)

然后问题就转化为了如何求 \(maxUV\)\(MaxUV\)

我们可以很容易想到暴力会 T 飞,所以我们选用倍增来解决,具体就是维护两个倍增数组 \(max1[i][j]\) 存放 i 点向上跳 \(2^{j}\) 步以后到的点之间的最大边权,\(max2[i][j]\) 存放 i 点向上跳 \(2^{j}\) 步以后到的点之间的次大边权,具体看下面代码的注释。

梳理一下流程:

  1. 先跑一边最小生成树

  2. 预处理出倍增数组 \(max1\)\(max2\)

  3. 倍增处理当前要加入的边 e 的起始点在最小生成树上的最大边权。

最后对所有的待选值取个 min 即可。

code:

#include<bits/stdc++.h>
#define int long long
#define INF (1ll<<62)
#define M 1000100
#define N 400010
using namespace std;
struct sb{int u,v,w,bt;}e[M];
int dep[N],f[N][21],max1[N][21],max2[N][21];
int n,m,mst,tot,head[M<<1],nxt[M<<1],v[M<<1],w[M<<1],fa[N];
inline int cmp(sb a,sb b){return a.w<b.w;}
inline int fid(int x){return fa[x]==x?x:fa[x]=fid(fa[x]);}
inline void add(int u,int v1,int w1){nxt[++tot]=head[u],head[u]=tot,v[tot]=v1,w[tot]=w1;}
inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}
inline void kruskal()//最小生成树板子,不会请退役 
{
	for(int i=1;i<=n;i++)fa[i]=i;
	sort(e+1,e+1+m,cmp);
	for(int i=1;i<=m;i++)
	{
		int u=e[i].u,v=e[i].v,w=e[i].w;
		if(fid(u)==fid(v))continue;
		mst+=w;
		e[i].bt=1;
		add(u,v,w);
		add(v,u,w);
		fa[fid(u)]=fid(v);
	}
}
inline void dfs(int x)//dfs预处理 
{
	for(int i=1;i<=18;i++)//处理倍增数组 
	{
		f[x][i]=f[f[x][i-1]][i-1];//处理倍增的父亲节点 
		max1[x][i]=max(max1[x][i-1],max1[f[x][i-1]][i-1]);//处理最大值,直接对两个区间取max 
		max2[x][i]=max(max2[x][i-1],max2[f[x][i-1]][i-1]);//先和上面一样取max 
		if(max1[x][i-1]!=max1[f[x][i-1]][i-1])//如果要是最大值的两个区间内的最大值不同 
		  max2[x][i]=max(max2[x][i],min(max1[x][i-1],max1[f[x][i-1]][i-1]));//从两个最大值里选个次小的 
	}
	for(int i=head[x];i;i=nxt[i])//遍历与当前点相连的边 
	{
		if(dep[v[i]])continue;//走过了就算了 
		f[v[i]][0]=x;//标记父节点 
		max1[v[i]][0]=w[i];//标记最大边权 
		max2[v[i]][0]=-INF;//标记次大边权 
		dep[v[i]]=dep[x]+1;//计算深度 
		dfs(v[i]);//继续搜 
	}
}
inline int LCA(int x,int y)//倍增求lca,不会请退役 
{
	if(dep[x]<dep[y])swap(x,y);
	for(int i=18;i>=0;i--)
	  if(dep[f[x][i]]>=dep[y])x=f[x][i];
	if(x==y)return x;
	for(int i=18;i>=0;i--)
	  if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
	return f[x][0];
}
inline int qmax(int x,int y,int z)//求x到y的边权最大值,z是要加进去的边权 
{
	int minn=-INF;//先设个最小值后面取min(设不设都行其实 
	for(int i=18;i>=0;i--)//倍增往上跳 
	{
		if(dep[f[x][i]]>=dep[y])//如果跳的时候不会超过就更新 
		{
			if(z!=max1[x][i])minn=max(minn,max1[x][i]);//情况1最大边权不等于z 
			else minn=max(minn,max2[x][i]);//情况二 最大边权等于z 
			x=f[x][i];//更新点的编号 
		}
	}
	return minn;//返回查到的需要替换的边权 
}
signed main()
{
	n=read(),m=read(); 
	for(int i=1;i<=m;i++)e[i].u=read(),e[i].v=read(),e[i].w=read();//输入边的信息 
	kruskal();//跑一边最小生成树 
	dep[1]=1;//赋初值第一个点深度为1 
	dfs(1);//从1开始往下搜 
	int Mst=INF;//存放次小生成树的值 
	for(int i=1;i<=m;i++)
	{
		int u=e[i].u,v=e[i].v,w=e[i].w;//起点终点边权 
		if(e[i].bt)continue;//如果是树边的话就跳过 
		int lca=LCA(u,v);//求LCA 
		int maxx=qmax(u,lca,w);//求出u到lca的边权最大值 
		int maxy=qmax(v,lca,w);//求出v到lca的边权最大值 
		Mst=min(Mst,mst-max(maxx,maxy)+w);//对于每一个去掉边权最大值取当前边权的方案取min 
	}
	cout<<Mst<<endl;//输出答案 
	return 0;
}

到了这里,关于最小生成树学习笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 编写函数,判断一个字符串是否是回文。在主函数中输入一个字符串,调用自定义函数,输出结果。 所谓回文是指顺读和倒读都一样的字符串。如“AMNMA“是回文。

    编写函数,判断一个字符串是否是回文。在主函数中输入一个字符串,调用自定义函数,输出结果。 所谓回文是指顺读和倒读都一样的字符串。如\\\"AMNMA\\\"是回文。 测试输入:abcba 测试输出:是回文! 这道题要求编写一个函数来判断一个字符串是否是回文,并在主函数中调用该

    2024年02月03日
    浏览(70)
  • 机器学习笔记之生成模型综述(一)生成模型介绍

    从本节开始,将介绍 生成模型 的相关概念。 生成模型,单从名字角度,可以将其认识为: 生成样本的模型 。从流程的角度,它可以理解为: 给定一个 数据集合 ,基于该数据集合进行建模,并通过 数据集合 学习出模型的参数信息; 根据已学习出的 参数信息 ,使用模型构

    2024年02月05日
    浏览(41)
  • 人工智能基础_机器学习006_有监督机器学习_正规方程的公式推导_最小二乘法_凸函数的判定---人工智能工作笔记0046

    我们来看一下公式的推导这部分比较难一些, 首先要记住公式,这个公式,不用自己理解,知道怎么用就行, 比如这个(mA)T 这个转置的关系要知道 然后我们看这个符号就是求X的导数,X导数的转置除以X的导数,就得到单位矩阵, 可以看到下面也是,各种X的导数,然后计算,得到对应的矩阵

    2024年02月08日
    浏览(60)
  • 人工智能基础_机器学习007_高斯分布_概率计算_最小二乘法推导_得出损失函数---人工智能工作笔记0047

    这个不分也是挺难的,但是之前有详细的,解释了,之前的文章中有, 那么这里会简单提一下,然后,继续向下学习 首先我们要知道高斯分布,也就是,正太分布, 这个可以预测x在多少的时候,概率最大 要知道在概率分布这个,高斯分布公式中,u代表平均值,然后西格玛代表标准差,知道了

    2024年02月07日
    浏览(77)
  • 深度学习笔记_1、定义神经网络

     

    2024年02月07日
    浏览(44)
  • Doris学习笔记-Java自定义UDAF

    项目最近需要做一些数据统计方面的东西,发现数据字段都是很长一串数字的字符串,Doris自带的函数无法对其进行相应的运算操作,需要扩展实现相关的操作运算。 主要参考官方的文档资料完成相关的自定义扩展。需要注意的是在使用Java代码编写UDAF时,有一些必须实现的

    2024年01月16日
    浏览(34)
  • QT学习笔记4--自定义信号的槽

    逻辑:下课后,老师饿了,学生请吃饭。 使用connect函数连接自定义的信号和槽函数。 声明   void classover();  

    2024年02月11日
    浏览(53)
  • 微信小程序学习笔记(四)——自定义组件

    创建组件 在根目录下创建 components 文件夹 右键点击 components 文件夹,选择 新建 Component ,就会自动生成 .wxml、.wxss、.js、.json 文件 引用组件 组件的引用方式分为“ 局部引用 ”和“ 全局引用 ”,故名思义: 局部引用 :组件只能在当前被引用的页面内使用 全局引用 :组件

    2024年02月16日
    浏览(52)
  • C语言——自定义类型结构体_学习笔记

    结构体是一种用户自定义的数据类型,可以包含 多个不同类型的变量 。通过使用结构体,我们可以将相关联的数据组织在一起,便于管理和使用。 在C语言中,结构体(struct)指的是一种数据结构,是C语言中聚合数据类型的一类。 结构体可以包含多个不同类型的数据成员,例

    2024年02月07日
    浏览(41)
  • WPF实战学习笔记22-添加自定义询问窗口

    详细代码:https://github.com/DongLiqiang/Mytodo/commit/221de6b2344d5c861f1d3b2fbb2480e3e3b35c26 添加自定义询问窗口显示方法 修改文件Mytodo.Extensions.DialogExtension 添加内容,类中添加内容 自定义窗体 自定义界面 添加文件Mytodo.Views.MsgView.xaml 添加窗体模型 添加文件:Mytodo.ViewModels.MsgViewModel 依赖

    2024年02月15日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包