2021-08-03
15:49:06
链接:
https://www.luogu.com.cn/problem/P2872
题目描述:
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.
给定 nn 个点的坐标,第 i 个点的坐标为 (xi,yi),这 n 个点编号为 1 到 n。给定 m 条边,第 i 条边连接第 ui 个点和第 vi 个点。现在要求你添加一些边,并且能使得任意一点都可以连通其他所有点。求添加的边的总长度的最小值。
输入格式
* 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 行每行两个整数 xi,yi 代表第 i 个点的坐标。
接下来 m 行每行两个整数 ui,vi 代表第 i 条边连接第 ui 个点和第 vi 个点。
输出格式
* 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 位实型变量进行计算。
输入输出样例
4 1
1 1
3 1
2 3
4 3
1 4
4.00
说明/提示
数据规模与约定
对于 100% 的整数,1≤n,m≤1000,1≤xi,yi≤10^6,1≤ui,vi≤n。
前言:
本题属于图论中的最小生成树题目,可以使用kruskal算法或prim算法解决,本篇博客使用prim算法
题意:
给定n个点和m条边,现在让你在图上连接一些点,使得所有的点都是连通的并且使得我们添加的边的长度最短,这里有一个技巧,就是:
若给出的其中两个点已经连接,我们就可以不需要在这两个点之间加边,所以这条边对我们添加的边的贡献就是0,所以我们在添加边的时候,将这条边的权重定义为0
数据范围:1≤n,m≤1000
第一行给出两个整数n,m
n:一共有n个点,
m:一共有m条边
我们使用结构体Node来储存所有的点
接下来有m条边,我们使用lianjie[][]记录点之间的连接情况。然后输入所有的数据
定义g[N][N]数组来储存每个点之间的距离,dist[N]表示各个点到连通块的距离
程序里面有一个函数 init() ,是用来初始化g[N][N]和dist[N]的
#include<iostream> using namespace std; const int N=1005; const int INF=100000000.0; int n,m; struct Node{ int x,y; }node[N]; bool lianjie[N][N];//lianjie[i][j]表示点i和点j是否连通
double dist[N];//dist[i]表示第i个点到连通块的距离
void init(){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)g[i][j]=INF;
for(int i=1;i<=n;i++)dist[i]=INF;
}
int main()
{
cin>>n>>m;
init();
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
node[i].x=x,node[i].y=y;
}
for(int i=1;i<=m;i++){
int from,to;
cin>>from>>to;
g[from][to]=g[to][from]=0;
lianjie[from][to]=true;
}
return 0;
}
因为题目给的每个点的信息是坐标,所以我们需要知道每个点之间的距离,所以我们得写一个distance()函数来计算点到点之间的距离
代码如下:
double distance(int x1,int y1,int x2,int y2){
double t=sqrt(pow((double)(x1-x2),2)+pow((double)(y1-y2),2));
return t;
}
注意!!!
distance()函数里面我所标红的两个地方都是需要对数进行double类型转化的,如果没有double类型转化,那么就会丢失精度,在本题里的#2,#8,#9,#10测试点就会 WA
至此,我们的代码为:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const int N=1005;
const int INF=100000000.0;
int n,m;
struct Node{
int x,y;
}node[N];
double dist[N];//dist[i]表示第i个点到连通块的距离
bool st[N];//st[i]表示点i和连通块是否连通
bool lianjie[N][N];//lianjie[i][j]表示点i和点j是否连通
double g[N][N];//g[i][j]表示点i到点j的距离,若不连通就是INF
double distance(int x1,int y1,int x2,int y2){
double t=sqrt(pow((double)(x1-x2),2)+pow((double)(y1-y2),2));
return t;
}
void init(){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)g[i][j]=INF;
for(int i=1;i<=n;i++)dist[i]=INF;
}
int main()
{
cin>>n>>m;
init();
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
node[i].x=x,node[i].y=y;
}
for(int i=1;i<=m;i++){
int from,to;
cin>>from>>to;
g[from][to]=g[to][from]=0;
lianjie[from][to]=true;
}
//计算每个每个点之间的距离
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(lianjie[i][j]||lianjie[j][i])g[i][j]=g[j][i]=0;
else g[j][i]=g[i][j]=distance(node[i].x,node[i].y,node[j].x,node[j].y);
}
}
double t=Prim();
printf("%.2f",t);
return 0;
}
我们假设prim函数已经写好,所以在程序的最后一点代码里有:
double t=Prim();
printf("%.2f",t);
注意:printf在输出double的时候," %f "和" %lf "是一样的,不会丢失精度,但是scanf在读入double的时候," %f "和" %lf "是不一样的
好,我们现在已经写好了除prim()函数以外的框架,那么我们接下来写prim函数
思路:
我们在前面的数据输入中已经处理了点到点的距离,点与点的连接关系。
我们以样例1为样例作说明:
如图
我们任取一个点,就从1号点开始吧,我们在函数运行的开始定义一个double类型变量res=0.0;这将会是函数的返回值,也是我们添加的边的最小值。
这里要讲一个概念:我们每次更新,都是在非连通块里面取点出来,取过的点就相当于加入了连通块,我们用st[N]数组来存储每个点是否已经在连通块里。刚开始连通块里面是空的,我们往里面加1号点。
如图
我们用绿色表示该点已经在连通块内了,然后用prim算法,求出非连通块点到连通块的距离,并取最短的那一个点,
我们可以看出,2,3,4号点到连通块的距离分别是根号5,0,根号5
所以我们把4号点加入连通块,并用1,4点的距离更新res
我们就可以得到下一个连通块了
res=0
然后再继续找离连通块最短的那个点
可以找到下一个点2(虽然3号点离连通块的距离最短也是2,但是按照遍历点顺序的是2)
把2加入连通块
res=2.00
最后只剩下3号点了,也加入连通块
res=4.00
double Prim(){
double res=0.0;
for(int i=1;i<=n;i++){
//下面这个循环是找不属于连通块点并且到连通块最近的点
int t=-1;
for(int j=1;j<=n;j++){
if(!st[j]&&(t==-1||dist[t]>dist[j]))
t=j;
}
st[t]=true;
if(i!=1)res+=dist[t];
//这里循环将更新每个非连通块点到连通块的最短距离
for(int j=1;j<=n;j++)dist[j]=min(dist[j],g[t][j]);
}
return res;
}
那么,到这里我们的prim函数就写完了,整装代码如下
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const int N=1005;
const int INF=100000000.0;
int n,m;
struct Node{
int x,y;
}node[N];
double dist[N];//dist[i]表示第i个点到连通块的距离
bool st[N];//st[i]表示点i和连通块是否连通
bool lianjie[N][N];//lianjie[i][j]表示点i和点j是否连通
double g[N][N];//g[i][j]表示点i到点j的距离,若不连通就是INF
double distance(int x1,int y1,int x2,int y2){
double t=sqrt(pow((double)(x1-x2),2)+pow((double)(y1-y2),2));
return t;
}
void init(){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)g[i][j]=INF;
for(int i=1;i<=n;i++)dist[i]=INF;
}
double Prim(){
double res=0.0;
for(int i=1;i<=n;i++){
int t=-1;
for(int j=1;j<=n;j++){
if(!st[j]&&(t==-1||dist[t]>dist[j]))
t=j;
}
st[t]=true;
if(i!=1)res+=dist[t];
for(int j=1;j<=n;j++)dist[j]=min(dist[j],g[t][j]);
}
return res;
}
int main()
{
cin>>n>>m;
init();
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
node[i].x=x,node[i].y=y;
}
for(int i=1;i<=m;i++){
int from,to;
cin>>from>>to;
g[from][to]=g[to][from]=0;
lianjie[from][to]=true;
}
//计算每个每个点之间的距离
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(lianjie[i][j]||lianjie[j][i])g[i][j]=g[j][i]=0;
else g[j][i]=g[i][j]=distance(node[i].x,node[i].y,node[j].x,node[j].y);
}
}
double t=Prim();
printf("%.2f",t);
return 0;
}
2021-08-03
16:38:12