最小生成树略解

最小生成树

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

Kruskal\text{Kruskal} 算法

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

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

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

Prim\text{Prim} 算法

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

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

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

题目描述 luoguP3366\text{luoguP3366}

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

Solution 3366\text{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}

原题来自:Waterloo University 2002

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

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

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

输入格式

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

输出格式

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

样例输入

3 2
10 10
10 0
30 0

样例输出

10.00

数据范围与提示

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

Solution 10065\text{Solution 10065}

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

#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);
			}
		}
	}
}
posted @ 2019-04-03 14:16  TeacherDai  阅读(224)  评论(0)    收藏  举报