Building Roads(Prim+Kruskal)

本文介绍了一个基于Prim和Kruskal算法解决农场间道路连接问题的方法。通过计算最小生成树来确定新建道路的最短总长度。

Building Roads
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 11767 Accepted: 3351
Description

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.

Input

  • 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.

Output

  • 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.

Sample Input

4 1
1 1
3 1
2 3
4 3
1 4
Sample Output

4.00
Source

USACO 2007 December Silver

n个村庄,m条已经修好的道路,接下来n行表示n个村庄的坐标(xi,yi),m行表示两个村庄(a,b)已经修好道路。求n个村庄互相连通需要修建的最短道路。

Prim:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#define INF 0x3f3f3f3f
double ma[1211][1211];
double dist[1211];
int v[1211];
int n, m;
int x[1211], y[1211];
double dis(int i, int j)
{
    double xx = x[i] - x[j];
    double yy = y[i] - y[j];
    return sqrt(xx*xx+yy*yy);
}
void Prim()
{
    memset(v, 0, sizeof(v));
    for(int i=0;i<n;i++)
        dist[i] = ma[0][i];
    v[0] = 1;
    int point;
    double ans = 0, min;
    for(int i=1;i<n;i++)
    {
        point = i;
        min = INF;
        for(int j=0;j<n;j++)
        {
            if(dist[j]<min&&v[j]==0)
            {
                point = j;
                min = dist[j];
            }
        }
        ans += min;
        v[point] = 1;
        for(int j=0;j<n;j++)
        {
            if(dist[j]>ma[point][j]&&v[j]==0)
                dist[j] = ma[point][j];
        }
    }
    printf("%.2f\n", ans);
}
int main()
{
    scanf("%d %d", &n, &m);
    for(int i=0;i<n;i++)
    {
        scanf("%d %d", &x[i], &y[i]);
    }
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<i;j++)
        {
            ma[i][j] = ma[j][i] = dis(i, j);
        }
    }
    for(int i=0;i<m;i++)
    {
        int a, b;
        scanf("%d %d", &a, &b);
        ma[a-1][b-1] = ma[b-1][a-1] = 0;//已经修好的清零
    }
    Prim();
    return 0;
}

Kruskal:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdio>
#define INF 0x3f3f3f3f
using namespace std;
double x[121001];
double y[121001];
struct node
{
    int x, y;
    double data;
}que[1000000];
int n, m, top, cont;
int pre[121001];
bool cmp(struct node u, struct node b)
{
    return u.data<b.data;
}
double dis(int i, int j)
{
    double xx = x[i] - x[j];
    double yy = y[i] - y[j];
    return sqrt(xx*xx+yy*yy);
}
int root(int a)
{
    while(a!=pre[a])
        a = pre[a];
    return a;
}
void Kruskal()
{
    double ans = 0;
    for(int i=0;i<=top&&cont!=n-1;i++)
    {
        int xx = root(que[i].x);
        int yy = root(que[i].y);
        if(xx!=yy)
            {
                pre[xx] = yy;
                ans += que[i].data;
                cont++;//记录边数
            }
    }
    printf("%.2f\n", ans);
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
     cin>>x[i]>>y[i];
    top = -1, cont = 0;
    for(int i=1;i<=n;i++)
    {
        for(int j=i+1;j<=n;j++)
        {
            top++;
            que[top].x = i;
            que[top].y = j;
            que[top].data = dis(i, j);
        }
    }
    for(int i=1;i<=n;i++)
        pre[i] = i;
    for(int i=0;i<m;i++)
    {
        int a, b;
        scanf("%d %d", &a, &b);
        int xx = root(a);
        int yy = root(b);
        if(xx!=yy)
            {
                pre[xx] = yy;
                cont++;
            }
    }
    sort(que, que+top+1, cmp);
    Kruskal();
    return 0;
}
### CSES 1666 Building Roads 问题解决方案 CSES 1666 Building Roads 是一个经典的图论问题,要求通过添加最少的边将一个不连通的无向图变成连通图。以下是解决问题的思路和实现方法。 #### 问题描述 给定一个包含 `n` 个节点和 `m` 条边的无向图,目标是通过添加最少数量的边使整个图连通。输出需要添加的边数以及这些边的具体连接方式。 --- #### 解决方案 该问题可以通过 **并查集(Union-Find)** 数据结构来高效解决。以下是具体步骤: 1. **初始化并查集**:为每个节点分配一个独立的集合。 2. **处理现有边**:对于每一条已存在的边 `(a, b)`,将节点 `a` 和 `b` 合并到同一个集合中。 3. **计算连通分量的数量**:遍历所有节点,统计当前图中存在的连通分量数目 `k`。 4. **确定需要添加的边数**:为了使图连通,至少需要添加 `k - 1` 条边。 5. **构造新边**:从不同的连通分量中选择节点对,构造新的边以连接这些分量。 --- #### 实现代码 以下是基于上述思路的完整实现代码: ```cpp #include <bits/stdc++.h> using namespace std; // 并查集实现 class UnionFind { public: vector<int> parent; UnionFind(int n) : parent(n + 1) { for (int i = 0; i <= n; ++i) parent[i] = i; } int find_set(int x) { if (parent[x] != x) parent[x] = find_set(parent[x]); return parent[x]; } void union_set(int x, int y) { int fx = find_set(x), fy = find_set(y); if (fx != fy) parent[fy] = fx; } }; int main() { ios::sync_with_stdio(false); cin.tie(0); int n, m; cin >> n >> m; UnionFind uf(n); for (int i = 0; i < m; ++i) { int a, b; cin >> a >> b; uf.union_set(a, b); } // 计算连通分量数量 int components = 0; vector<int> representatives; for (int i = 1; i <= n; ++i) { if (uf.find_set(i) == i) { components++; representatives.push_back(i); } } // 输出结果 if (components == 1) { cout << "0\n"; } else { cout << components - 1 << "\n"; for (int i = 1; i < components; ++i) { cout << representatives[0] << " " << representatives[i] << "\n"; } } return 0; } ``` --- #### 代码解释 1. **并查集初始化**:使用 `UnionFind` 类来管理节点的集合关系[^4]。 2. **合并操作**:对于输入中的每条边 `(a, b)`,调用 `union_set(a, b)` 将两个节点合并到同一集合中。 3. **连通分量统计**:通过检查每个节点的根节点是否唯一,统计连通分量的数量。 4. **构造新边**:如果连通分量数量大于 1,则需要添加 `k - 1` 条边,连接不同连通分量的代表节点。 --- #### 时间复杂度分析 - **初始化并查集**:`O(n)` - **处理现有边**:`O(m * α(n))`,其中 `α(n)` 是逆阿克曼函数,几乎为常数。 - **统计连通分量**:`O(n * α(n))` - **构造新边**:`O(k)`,其中 `k` 是连通分量的数量。 总时间复杂度为 `O(n + m * α(n))`,适用于大规模输入。 --- ### 注意事项 - 如果图本身已经连通,则无需添加任何边,直接输出 `0`。 - 确保输入数据符合题目要求,避免越界访问或非法输入。 --- ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值