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 个点的坐标,第 个点的坐标为 (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 位实型变量进行计算。

输入输出样例

输入 #1
4 1
1 1
3 1
2 3
4 3
1 4
输出 #1
4.00

说明/提示

数据规模与约定

对于 100% 的整数,1n,m1000,1xi,yi10^6,1ui,vin。

 

 

前言:

本题属于图论中的最小生成树题目,可以使用kruskal算法或prim算法解决,本篇博客使用prim算法

 

题意:

给定n个点和m条边,现在让你在图上连接一些点,使得所有的点都是连通的并且使得我们添加的边的长度最短,这里有一个技巧,就是:

若给出的其中两个点已经连接,我们就可以不需要在这两个点之间加边,所以这条边对我们添加的边的贡献就是0,所以我们在添加边的时候,将这条边的权重定义为0

数据范围:1n,m1000

第一行给出两个整数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

  

 

posted on 2021-08-03 16:38  Dragon昴  阅读(127)  评论(0编辑  收藏  举报