最小生成树
一个有 n n n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n n n 个结点,并且有保持图连通的最少的边。最小生成树是最小权重生成树的简称。
Kruskal \text{Kruskal} Kruskal 算法
使用 Kruskal \text{Kruskal} Kruskal 算法求出一无向图 G G G 的最小生成树步骤如下:
- 将边按照权升序排序;
- 从第 1 1 1 条边开始,判断两端点是否联通;若不联通,则标记该边;
- 重复 2. 操作,直到有 n − 1 n-1 n−1 条标记的边。( 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 的最小生成树步骤如下:
- 任意标记一点,将它加入 V 2 V_2 V2 集合,异于它的点加入 V 1 V_1 V1 集合;
- 找出与 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;
- 重复 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 1≤n≤500,0≤x,y≤104,0≤k≤100。
Solution 10065 \text{Solution 10065} Solution 10065
显然,应尽量把卫星电话给距离最远的村庄使用。
反向考虑问题。找出
n
−
k
n-k
n−k 个点,使它们联通的代价最小。剩下的
k
k
k 个点获得卫星电话。那么,最小的
d
d
d 值就是联通这
n
−
k
n-k
n−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);
}
}
}
}