最小生成树学习笔记

总结:

  1. 当点上有权值信息时,我们考虑 建立超级源点,将超级源点与 n 个点分别连一条权值大小为该点上权值的大小的边。

一: $Prim$ 算法(稠密图)

$Prim$ 算法总是维护最小生成树的一部分。起初,$Prim$ 算法仅确定1号节点属于最小生成树。
在任意时刻,设已经确定属于最小生成树的节点集合为 $T$,剩余节点集合为 $S$
$x\in S$,则 $d[x]$ 表示节点 $x$ 与集合 $T$ 中的节点之间权值最小的边的权值。  
$x\in T$,则 $d[x]$ 就等于 $x$ 被加入 $T$ 时选出的最小边的权值。

int prim(){
	int res=0;
	memset(dist,0x3f,sizeof(dist));
	dist[1]=0;
	for(int i=0;i<n;i++){
		int t=-1;
		for(int j=1;j<=n;j++){
			if(!vis[j]&&(t==-1||dist[t]>dist[j]))
				t=j;
		}
			
		res+=dist[t];
		vis[t]=1;
		for(int j=1;j<=n;j++) dist[j]=min(dist[j],w[t][j]);
	}
	return res;
}

二: $Kruskal$ 算法

$Kruskal$ 算法 总是维护无向图的最小生成森林。最初,可认为生成森林由 $0$ 条边构成,每个节点各自构成一棵仅包含一个点的树。

$Kruskal$ 算法则是将所有边按权值从小到大排序,在任意时刻, $Kruskal$ 算法从剩余的边中选出一条权值最小的,并且这条边的两端点属于生成森林中两棵不同的树(不连通),把该边加入生成树林。图中节点的联通情况可以用并查集维护。

struct node{
	int a,b,w;
}e[M];
int n,m,fa[N],res=0;
int find(int x){
	if(fa[x]==x) return fa[x];
	return fa[x]=find(fa[x]);
}
bool cmp(node x,node y){
	return x.w<y.w;
}
void Kruskal(){
	for(int i=1;i<=n;i++) fa[i]=i;
	sort(e+1,e+1+m,cmp);
	res=0;
	for(int i=1;i<=m;i++){
		int pa=find(e[i].a),pb=find(e[i].b);
		if(pa==pb) continue;
		fa[pb]=pa;
		res+=e[i].w;
	}
}

例题:

基础应用

1. AcWing 1140. 最短网络

最短路模板题,用 Prim 算法解决

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,w[N][N],dist[N];
int vis[N];
int prim(){
	int res=0;
	memset(dist,0x3f,sizeof(dist));
	dist[1]=0;
	for(int i=0;i<n;i++){
		int t=-1;
		for(int j=1;j<=n;j++){
			if(!vis[j]&&(t==-1||dist[t]>dist[j]))
				t=j;
		}
			
		res+=dist[t];
		vis[t]=1;
		for(int j=1;j<=n;j++) dist[j]=min(dist[j],w[t][j]);
	}
	return res;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			scanf("%d",&w[i][j]);
	printf("%d\n",prim());
	return 0;
}
2. AcWing 1141. 局域网

此题求出最小生成树,答案等于 将不属于最小生成树的边 做累加和

#include<bits/stdc++.h>
using namespace std;
const int N=110,M=210;
struct node{
	int a,b,w;
}e[M];
int n,m,fa[N];
int find(int x){
	if(fa[x]==x) return fa[x];
	return fa[x]=find(fa[x]);
}
bool cmp(node x,node y){
	return x.w<y.w;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
		scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].w);
	for(int i=1;i<=n;i++) fa[i]=i;
	sort(e+1,e+1+m,cmp);
	int res=0;
	for(int i=1;i<=m;i++){
		int pa=find(e[i].a),pb=find(e[i].b);
		if(pa==pb){
			res+=e[i].w;
			continue;
		}
		fa[pb]=pa;
	}
	printf("%d\n",res);
	return 0;
}
3. AcWing 1142. 繁忙的都市

求出最小生成树的所有边,树边最大值即为答案

#include<bits/stdc++.h>
using namespace std;
const int N=310,M=8010;
struct node{
	int a,b,w;
}e[M];
int n,m,fa[N];
int find(int x){
	if(fa[x]==x) return fa[x];
	return fa[x]=find(fa[x]);
}
bool cmp(node x,node y){
	return x.w<y.w;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
		scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].w);
	sort(e+1,e+1+m,cmp);
	int ans=0;
	for(int i=1;i<=m;i++){
		int pa=find(e[i].a),pb=find(e[i].b);
		if(pa==pb) continue;
		fa[pb]=pa;
		ans=max(ans,e[i].w);
	}
	printf("%d %d\n",n-1,ans);
	return 0;
}
4. AcWing 1143. 联络员

本题先将所有的必选边加入到生成森林中,然后将剩下的边从小到大排序,将边的两点 不属于同一颗树的边加到生成森林中,最终生成一颗树。 答案即为该树上边权值的累加和。

#include<bits/stdc++.h>
using namespace std;
const int N=2010,M=10010;
struct node{
	int a,b,w;
}e[M];
int n,m,fa[N],cnt;
int find(int x){
	if(fa[x]==x) return fa[x];
	return fa[x]=find(fa[x]);
}
bool cmp(node x,node y){
	return x.w<y.w;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) fa[i]=i;
	int ans=0;
	for(int i=1;i<=m;i++){
		int a,b,w,p;
		scanf("%d%d%d%d",&p,&a,&b,&w);
		if(p==1){
			ans+=w;
			int pa=find(a),pb=find(b);
			fa[pb]=pa;
		}
		else e[++cnt]={a,b,w};
	}
	sort(e+1,e+1+cnt,cmp);
	for(int i=1;i<=cnt;i++){
		int pa=find(e[i].a),pb=find(e[i].b);
		if(pa==pb) continue;
		fa[pb]=pa;
		ans+=e[i].w;
	}
	printf("%d\n",ans);
	return 0;
}

扩展应用

1. AcWing 1146. 新的开始

使得矿井有电,要么矿井自己建立发电站,要么与其他的的矿井建立电网,因此我们可以建立一个超级源点,该超级源点与每一个矿井连一条权值为 w[i] 的边, w[i] 为在该矿井上建立发电站的费用。

之后我们再在该图上求出最小生成树,此时的最小生成树一共有 n+1 个点,n 条边(因为加了一个超级源点)。答案即为该最小生成树的边权的累加和。

#include<bits/stdc++.h>
using namespace std;
const int N=310,M=90010;
struct node{
	int a,b,w;
}e[M];
int n,cnt,fa[N];
int find(int x){
	if(fa[x]==x) return fa[x];
	return fa[x]=find(fa[x]);
}
bool cmp(node x,node y){
	return x.w<y.w;
}
int main(){
	scanf("%d",&n);
	for(int i=0;i<=n;i++) fa[i]=i;
	for(int i=1;i<=n;i++){
		scanf("%d",&e[++cnt].w);
		e[cnt].a=0; e[cnt].b=i;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			int w;
			scanf("%d",&w);
			if(i==j) continue;
			e[++cnt].w=w; e[cnt].a=i; e[cnt].b=j; 
		}
	sort(e+1,e+1+cnt,cmp);
	int ans=0;
	for(int i=1;i<=cnt;i++){
		int pa=find(e[i].a),pb=find(e[i].b);
		if(pa==pb) continue;
		fa[pb]=pa;
		ans+=e[i].w;
	}
	printf("%d\n",ans);
	return 0;
}
2. AcWing 1145. 北极通讯网络

我们先将该图生成一颗最小生成树,对于卫星通讯,我们肯定是将其分发给该最小生成树的最长的边的某一个端点,可以使得最长的边最小,因此每减去一条边,就有一个点和该生成树脱离,自我形成一个独立的点,当减去 k-1 条边,此时有 k 个连通块,即每个连通块分发一个卫星通讯,因此答案为 剩余的边 组成的最小生成树的最大的边的权值。

#include<bits/stdc++.h>
using namespace std;
const int N=510,M=N*N;
struct node{
	int a,b;
	double w;
}e[M],edge[M];
struct dot{
	int x,y;
}vil[N];
int n,k,cnt,fa[N],idx,vis[N];
int find(int x){
	if(fa[x]==x) return fa[x];
	return fa[x]=find(fa[x]);
}
double get_w(dot t1,dot t2){
	int dx=t1.x-t2.x,dy=t1.y-t2.y;
	return sqrt((double)dx*dx*1.0+(double)dy*dy*1.0);
}
bool cmp(node x,node y){
	return x.w<y.w;
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&vil[i].x,&vil[i].y);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++){
			e[++cnt].a=i; e[cnt].b=j;
			e[cnt].w=get_w(vil[i],vil[j]);
		}
	sort(e+1,e+1+cnt,cmp);
	for(int i=1;i<=cnt;i++){
		int pa=find(e[i].a),pb=find(e[i].b);
		if(pa==pb) continue;
		fa[pb]=pa;
		edge[++idx]=e[i];
	}
	double ans=0;
// 	printf("%d\n",idx);
// 	for(int i=1;i<=idx;i++) printf("%d %d %lf\n",edge[i].a,edge[i].b,edge[i].w);
	k--;
    for(int i=idx;i>=1;i--){
		if(k<=0){
			ans=edge[i].w;
			break;
		}
		k--;
	}
	printf("%.2lf\n",ans);
	return 0;	
}
3. AcWing 346. 走廊泼水节

题目大意:将一颗树 加边 扩展为一个 完全图,并且不影响在此完全图上求最小生成树仍是之前的那颗原来的树,将加入的边的权值和输出。

y总做题思路:

 

因此我们将该树的边按权值从小到大排序,若该边两端 a,b 属于不同的树,则将 a 所在的树中的每一个点与 b 所在的树中的每一个点连一条边,其权值为 w[a][b]+1,我们将多连的 (a,b) 这条边去掉,因此对答案的贡献为 (size[a]*size[b]-1)*(w[a][b]+1)

#include<bits/stdc++.h>
using namespace std;
const int N=6010;
struct node{
	int a,b,w;
}e[N];
int T,n,fa[N],siz[N];

int find(int x){
	if(fa[x]==x) return fa[x];
	return fa[x]=find(fa[x]);
}
bool cmp(node x,node y){
	return x.w<y.w;
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		for(int i=1;i<n;i++){
			int a,b,w;
			scanf("%d%d%d",&a,&b,&w);
			e[i]={a,b,w};
		}
		for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
		sort(e+1,e+n,cmp);
		int ans=0;
		for(int i=1;i<n;i++){
			int pa=find(e[i].a),pb=find(e[i].b);
			if(pa==pb) continue;
			ans=ans+(siz[pa]*siz[pb]-1)*(e[i].w+1);
			fa[pb]=pa;
			siz[pa]+=siz[pb];
		}
		printf("%d\n",ans);
	}
	return 0;
}
4. AcWing 1148. 秘密的牛奶运输
严格次小生成树模板题

 y总的讲解适用于 非严格次小生成树 ,对于 严格次小生成树,我们应该记录最小生成树上每一对点的路径上的 最大值 d1[a][b] 和 次大值 d2[a][b] ,最大值和次大值严格不相等。设 sum 为最小生成树上所有边权的累加和。对于每一条非树边,非树边两端端点分别为 a,b ,非树边的权值为 w,若 a 到 b 的路径上的最大值满足 d1[a][b]<w ,则 res=min(res,sum-d1[a][b]+w),若  a 到 b 的路径上的次大值满足 d2[a][b]<w<d1[a][b],则 res=min(res,sum-d2[a][b]+w)

代码 1(非 LCA版本)(n^2 版本):

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=510,M=1e4+10;
struct node{
	int nex,to,w;
}e[N*2];
struct edges{
	int a,b,w;
	bool f;
}edge[M];
int n,m,head[N],cnt,fa[N],d1[N][N],d2[N][N];
bool cmp(edges x,edges y){
	return x.w<y.w;
}
int find(int x){
	if(fa[x]==x) return fa[x];
	return fa[x]=find(fa[x]);
}
void add(int u,int v,int w){
	e[++cnt].nex=head[u];
	e[cnt].to=v;
	e[cnt].w=w;
	head[u]=cnt;
}
void dfs(int u,int fa,int max1,int max2,int dis1[],int dis2[]){
	dis1[u]=max1; dis2[u]=max2;
	for(int i=head[u];i;i=e[i].nex){
		int v=e[i].to,w=e[i].w;
		if(v==fa) continue;
		int td1=max1,td2=max2;
		if(w>td1) td2=td1,td1=w;
		else if(w>td2&&w<td1) td2=w;
		dfs(v,u,td1,td2,dis1,dis2);
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++){
		int a,b,w;
		scanf("%d%d%d",&a,&b,&w);
		edge[i]={a,b,w};
	}
	sort(edge+1,edge+1+m,cmp);
	ll ans=0;
	for(int i=1;i<=m;i++){
		int a=edge[i].a,b=edge[i].b,w=edge[i].w;
		int pa=find(a),pb=find(b);
		if(pa==pb) continue;
		fa[pb]=pa;
		edge[i].f=true;
		ans+=w;
		add(a,b,w); add(b,a,w);
	}
	for(int i=1;i<=n;i++) dfs(i,0,0,0,d1[i],d2[i]);
	ll res=1e18;
	for(int i=1;i<=m;i++){
		if(edge[i].f) continue;
		int a=edge[i].a,b=edge[i].b,w=edge[i].w;
		if(w>d1[a][b]) res=min(res,ans-d1[a][b]+w);
		else if(w>d2[a][b]&&d2[a][b]) res=min(res,ans-d2[a][b]+w);

	}
	printf("%lld\n",res);
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值