最小生成树略解

最小生成树

一个有 n n n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n n n 个结点,并且有保持图连通的最少的边。最小生成树是最小权重生成树的简称。

Kruskal \text{Kruskal} Kruskal 算法

使用 Kruskal \text{Kruskal} Kruskal 算法求出一无向图 G G G 的最小生成树步骤如下:

  1. 将边按照权升序排序;
  2. 从第 1 1 1 条边开始,判断两端点是否联通;若不联通,则标记该边;
  3. 重复 2. 操作,直到有 n − 1 n-1 n1 条标记的边。( n n n 是点数)标记的边组成的集合就是 G G G 的最小生成树。

对于操作 2. 中斜体部分,使用并查集判断两点的连通性。
时间复杂度: O ( m log ⁡ m + m α ( n ) ) O(m\log m+m\alpha(n)) O(mlogm+mα(n)) n n n 是点数, m m m 是边数, α ( n ) \alpha(n) α(n) 是一次并查集的复杂度。

Prim \text{Prim} Prim 算法

使用 Prim \text{Prim} Prim 算法求出一无向图 G G G 的最小生成树步骤如下:

  1. 任意标记一点,将它加入 V 2 V_2 V2 集合,异于它的点加入 V 1 V_1 V1 集合;
  2. 找出与 V 2 V_2 V2 中一点的距离最短的、属于 V 1 V_1 V1 的点;标记连接该点 u u u V 2 V_2 V2 中与该点距离最短的点 v v v ( u , v ) (u,v) (u,v),将 v v v 移出 V 1 V_1 V1 并加入 V 2 V_2 V2
  3. 重复 2. 操作,直到 V 1 V_1 V1 变为空集。标记的边组成的集合就是 G G G 的最小生成树。

对于操作 2. 中的斜体部分,使用堆优化。
时间复杂度: O ( ( n + m ) log ⁡ m ) O((n+m)\log m) O((n+m)logm) n n n 是点数, m m m 是边数。

题目描述 luoguP3366 \text{luoguP3366} luoguP3366

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz

Solution 3366 \text{Solution 3366} Solution 3366

模板,无须赘述。贴上 Kruskal 算法的代码。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

#define reg register

struct node{
	int x,y,d;
}e[400010];
int len=0;
int n,m;
int s1,s2,s3;
int f[5010];

void ins(int x,int y,int d){
	e[++len].x=x;e[len].y=y;e[len].d=d;
}
int cmp(node a,node b){
	return a.d<b.d;
}
int findfa(int x){
	if(f[x]==x) return x;
	return f[x]=findfa(f[x]);
}
int main(){
	scanf("%d%d",&n,&m);
	for(reg int i=1;i<=m;++i){
		scanf("%d%d%d",&s1,&s2,&s3);
		ins(s1,s2,s3);ins(s2,s1,s3);
	}
	std::sort(e+1,e+len+1,cmp);
	for(reg int i=1;i<=n;++i)
		f[i]=i;
	int cnt=0,ans=0;
	for(reg int i=1;i<=len;++i){
		int x=e[i].x,y=e[i].y;
		int fx=findfa(x),fy=findfa(y);
		if(fx!=fy){
			f[fx]=fy;	//注意不是 f[x]=fy
			++cnt;ans+=e[i].d;
			if(cnt==n-1) break;
		}
	}
	printf("%d",ans);
}

题目描述 loj10065 \text{loj10065} loj10065

原题来自:Waterloo University 2002

北极的某区域共有 n n n 座村庄,每座村庄的坐标用一对整数 ( x , y ) (x,y) (x,y) 表示。为了加强联系,决定在村庄之间建立通讯网络。通讯工具可以是无线电收发机,也可以是卫星设备。所有的村庄都可以拥有一部无线电收发机, 且所有的无线电收发机型号相同。但卫星设备数量有限,只能给一部分村庄配备卫星设备。

不同型号的无线电收发机有一个不同的参数 d d d,两座村庄之间的距离如果不超过 d d d 就可以用该型号的无线电收发机直接通讯, d d d 值越大的型号价格越贵。拥有卫星设备的两座村庄无论相距多远都可以直接通讯。

现在有 k k k 台卫星设备,请你编一个程序,计算出应该如何分配这 k k k 台卫星设备,才能使所拥有的无线电收发机的 d d d 值最小,并保证每两座村庄之间都可以直接或间接地通讯。

输入格式

第一行为由空格隔开的两个整数 n , k n,k n,k;
2 2 2 n + 1 n+1 n+1 行,每行两个整数,第 i i i 行的 x i , y i x_i,y_i xi,yi 表示第 i i i 座村庄的坐标 ( x i , y i ) (x_i,y_i) (xi,yi)

输出格式

一个实数,表示最小的 d d d 值,结果保留 2 2 2 位小数。

样例输入

3 2
10 10
10 0
30 0

样例输出

10.00

数据范围与提示

对于全部数据, 1 ≤ n ≤ 500 , 0 ≤ x , y ≤ 1 0 4 , 0 ≤ k ≤ 100 1\leq n\leq 500,0\leq x,y\leq 10^4,0\leq k\leq 100 1n500,0x,y104,0k100

Solution 10065 \text{Solution 10065} Solution 10065

显然,应尽量把卫星电话给距离最远的村庄使用。
反向考虑问题。找出 n − k n-k nk 个点,使它们联通的代价最小。剩下的 k k k 个点获得卫星电话。那么,最小的 d d d 值就是联通这 n − k n-k nk 个点的边权最大边的权。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>

#define reg register

struct node{
	int x,y,next;
	double d;
}e[126000];
int len=0;
int first[510];
int n,k;
double xx[510],yy[510];
int sum=0;
int f[510];

double getd(double a,double b,double c,double d){
	return sqrt((a-b)*(a-b)+(c-d)*(c-d));
}
void ins(int x,int y){
	e[++len].x=x;e[len].y=y;e[len].d=getd(xx[x],xx[y],yy[x],yy[y]);
	e[len].next=first[x];first[x]=len;
}
int cmp(node a,node b){
	return a.d-b.d<0.000000001;	//这样操作更加优秀
}
int findfa(int x){
	if(f[x]==x) return x;
	return f[x]=findfa(f[x]);
}
int main(){
	scanf("%d%d",&n,&k);
	if(k>=n){
		puts("0.00");
		exit(0);
	}
	for(reg int i=1;i<=n;++i){
		f[i]=i;
		scanf("%lf%lf",&xx[i],&yy[i]);
		for(reg int j=1;j<i;++j)
			ins(i,j);
	}
	std::sort(e+1,e+len+1,cmp);
	for(reg int i=1;i<=len;++i){
		int x=e[i].x,y=e[i].y,fx=findfa(x),fy=findfa(y);
		if(fx!=fy){
			++sum;
			f[fy]=fx;
			if(sum==n-k){
				printf("%.2lf",e[i].d);
				exit(0);
			}
		}
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值