# [USACO07DEC]Building Roads S
## 题目描述
Farmer John had just acquired several new farms! He wants to connect the farms with roads so that he can travel from any farm to any other farm via a sequence of roads; roads already connect some of the farms.
Each of the N (1 ≤ N ≤ 1,000) farms (conveniently numbered 1..N) is represented by a position (Xi, Yi) on the plane (0 ≤ Xi ≤ 1,000,000; 0 ≤ Yi ≤ 1,000,000). Given the preexisting M roads (1 ≤ M ≤ 1,000) as pairs of connected farms, help Farmer John determine the smallest length of additional roads he must build to connect all his farms.
给定 $n$ 个点的坐标,第 $i$ 个点的坐标为 $(x_i,y_i)$,这 $n$ 个点编号为 $1$ 到 $n$。给定 $m$ 条边,第 $i$ 条边连接第 $u_i$ 个点和第 $v_i$ 个点。现在要求你添加一些边,并且能使得任意一点都可以连通其他所有点。求添加的边的总长度的最小值。
## 输入格式
\* Line 1: Two space-separated integers: N and M
\* Lines 2..N+1: Two space-separated integers: Xi and Yi
\* Lines N+2..N+M+2: Two space-separated integers: i and j, indicating that there is already a road connecting the farm i and farm j.
第一行两个整数 $n,m$ 代表点数与边数。
接下来 $n$ 行每行两个整数 $x_i,y_i$ 代表第 $i$ 个点的坐标。
接下来 $m$ 行每行两个整数 $u_i,v_i$ 代表第 $i$ 条边连接第 $u_i$ 个点和第 $v_i$ 个点。## 输出格式
\* Line 1: Smallest length of additional roads required to connect all farms, printed without rounding to two decimal places. Be sure to calculate distances as 64-bit floating point numbers.
一行一个实数代表添加的边的最小长度,要求保留两位小数,为了避免误差, 请用 $64$ 位实型变量进行计算。
## 样例 #1
### 样例输入 #1
```
4 1
1 1
3 1
2 3
4 3
1 4
```### 样例输出 #1
```
4.00
```## 提示
### 数据规模与约定
对于 $100\%$ 的整数,$1 \le n,m \le 1000$,$1 \le x_i,y_i \le 10^6$,$1 \le u_i,v_i \le n$。
### 说明
Translated by 一只书虫仔。
1.这道题是最小生成树问题,可以使用prim,也可以用kruskal算法。
起初我使用的是prim算法,比较暴力的那种prim算法,去依次筛选距离最小生成树最小值,很遗憾只拿了70分,时间超限了,下面是我70分的代码。
#include<stdio.h>
#include<math.h>
#define N 10010
struct node
{
int x;
int y;
}vertex[N];
int n,m,f[N];
double dis[N];
int getf(int x)
{
if(x==f[x]) return x;
f[x]=getf(f[x]);
}
int merge(int a,int b)
{
int p,q;
p=getf(a);
q=getf(b);
if(p!=q)
{
f[q]=p;
return 1;
}
else return 0;
}
double min(double a,double b)
{
if(a>b) return b;
return a;
}
double distance(struct node a,struct node b)
{
return sqrt((double)(a.x-b.x)*(a.x-b.x)+(double)(a.y-b.y)*(a.y-b.y));
}
int fun()
{
int tree=1,i,j,k,ares;
double nox=2147483647,sum=0;
// book[1]=1;//确定第一个点为最小生成树
while(tree<n)//如果树已经到了所有的点,
//表明最小生成树已经构建完毕
{
//需要刷新最小生成树到其他点的所有距离
//并且找到最小值
ares=0;
for(i=1;i<=n;i++)//找所有在树里面的点
{
if(getf(i)==1)//如果是
{
ares++;
for(j=1;j<=n;j++)//刷新其他点
{
if(getf(j)!=1)
{
dis[j]=min(dis[j],distance(vertex[i],vertex[j]));
}
}
}
}
//找距离最小生成树最短的点
nox=2147483647;
for(i=1;i<=n;i++)
{
if(getf(i)!=1&&nox>dis[i])
{
nox=dis[i];
j=i;
}
}
sum+=dis[j];
k=getf(j);
//这个最短的点是否还附带了其他点,把他们纳入最小生成树
for(i=1;i<=n;i++)
{
if(k==getf(i))//如果有
{
f[getf(i)]=1;//变成最小生成树
//tree++;
}
}
f[k]=1;
tree=ares;
}
// for(i=1;i<=n;i++)
// printf("%.2f ",dis[i]);
// puts("");
printf("%.2f",sum);
}
int main()
{
int i,j,u,v;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
f[i]=i,dis[i]=2147483647;
for(i=1;i<=n;i++)
scanf("%d%d",&vertex[i].x,&vertex[i].y);
for(i=0;i<m;i++)
{
scanf("%d%d",&u,&v);
merge(u,v);
}
fun();
return 0;
}
但是我又不太会写有关堆优化的prim算法,只是知道堆排序和优先队列的概念。
然后去看了题解,发现kruskal算法也可以实现,于是我又写了kruskal的代码,然后AC了90分。
这里就有一个小重点了:如果节点已经有路径了,我们就将该边路径设置为0,即使后面加上也不会影响答案。在遍历边的时候,我们把已经有路径的俩个节点进行并查集合并,并且tree(存储最小生成树里面已经有几点)也是可以++的(我忘记了,但是还是AC了),最后去遍历边存储到数组里面,我们只需要排序这些边即可,那些已经在最小生成树里面的就不必排序了,会节省很多时间(我就是因为没有这么写,导致最后一个案列TLE了)
C代码如下:
#include<stdio.h>
#include<math.h>
#define N 10010
struct node
{
int x;
int y;
}vertex[N];
struct ares
{
int u,v;
double w;
}edges[N];
int n,m,f[N];
char book[N][N];
int getf(int x)
{
if(x==f[x]) return x;
return f[x]=getf(f[x]);
}
int merge(int a,int b)
{
int p,q;
p=getf(a);
q=getf(b);
if(p!=q)
{
f[q]=p;
return 1;
}
return 0;
}
double min(double a,double b)
{
if(a>b) return b;
return a;
}
double distance(struct node a,struct node b)
{
return sqrt((double)(a.x-b.x)*(a.x-b.x)+(double)(a.y-b.y)*(a.y-b.y));
}
int quicksort(int left,int right)
{
if(left>=right) return 0;
int i=left,j=right;
struct ares t,temp=edges[left];
while(i<j)
{
while(i<j&&temp.w<=edges[j].w) j--;
while(i<j&&temp.w>=edges[i].w) i++;
if(i<j)
{
t=edges[i];edges[i]=edges[j];edges[j]=t;
}
}
edges[left]=edges[i];
edges[i]=temp;
quicksort(left,i-1);
quicksort(i+1,right);
}
int fun(int k)
{
int i,j,tree=1;
double sum=0;
for(i=0;i<k;i++)
{
if(merge(edges[i].u,edges[i].v))
{
sum+=edges[i].w;
tree++;if(tree>n) break;
}
}
printf("%.2f",sum);
return 0;
}
int main()
{
int i,j,u,v,k=0;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{
f[i]=i;
scanf("%d%d",&vertex[i].x,&vertex[i].y);
}
for(i=0;i<m;i++)
{
scanf("%d%d",&u,&v);
merge(u,v);
}
for(i=1;i<=n;i++)
{
for(j=1;j<i;j++)
{
edges[k].u=i;
edges[k].v=j;
edges[k].w=distance(vertex[i],vertex[j]);
k++;
}
}
quicksort(0,k-1);
// for(i=0;i<k;i++)
// printf("%.2f ",edges[i].w);
fun(k);
return 0;
}
C++代码如下:
#include<iostream>
#include<cstdio>
#include<bits/stdc++.h>
#include<cmath>
using namespace std;
const int N=10010;
struct node
{
int x;
int y;
}vertex[N];
struct ares
{
int u,v;
double w;
}edges[N];
int n,m,f[N];
int getf(int x)
{
if(x==f[x]) return x;
return f[x]=getf(f[x]);
}
int merge(int a,int b)
{
int p,q;
p=getf(a);
q=getf(b);
if(p!=q)
{
f[q]=p;
return 1;
}
return 0;
}
double distance(node a,node b)
{
return sqrt((double)(a.x-b.x)*(a.x-b.x)+(double)(a.y-b.y)*(a.y-b.y));
}
bool cmp(ares a,ares b)
{
return a.w<b.w;
}
int fun(int k)
{
int i,j,tree=1;
double sum=0;
for(i=0;i<k;i++)
{
if(merge(edges[i].u,edges[i].v))
{
sum+=edges[i].w;
tree++;
if(tree>n) break;
}
}
printf("%.2f",sum);
return 0;
}
int main()
{
int i,j,u,v,k=0;
cin >> n >> m ;
for(i=1;i<=n;i++)
{
f[i]=i;
cin >> vertex[i].x >> vertex[i].y ;
}
for(i=0;i<m;i++)
{
cin >> u >> v ;
merge(u,v);
}
for(i=1;i<=n;i++)
{
for(j=1;j<i;j++)
{
edges[k].u=i;
edges[k].v=j;
edges[k].w=distance(vertex[i],vertex[j]);
k++;
}
}
sort(vertex,vertex+k-1,cmp);
fun(k);
return 0;
}