AcWing 1145. 北极通讯网络

\(AcWing\) \(1145\). 北极通讯网络

一、题目描述

北极的某区域共有 \(n\) 座村庄,每座村庄的坐标用一对整数 \((x,y)\) 表示。

为了加强联系,决定在村庄之间建立通讯网络,使每两座村庄之间都可以 直接间接 通讯。

通讯工具可以是无线电收发机,也可以是卫星设备。

无线电收发机有多种不同型号,不同型号的无线电收发机有一个不同的参数 \(d\),两座村庄之间的距离如果不超过 \(d\),就可以用该型号的无线电收发机直接通讯,\(d\) 值越大的型号价格越贵。现在要先选择某一种型号的无线电收发机,然后统一给所有村庄配备,数量不限,但型号都是 相同的

配备卫星设备的两座村庄 无论相距多远都可以直接通讯,但卫星设备是 有限 的,只能给一部分村庄配备。

现在有 \(k\) 台卫星设备,请你编一个程序,计算出应该如何分配这 \(k\) 台卫星设备,才能使所配备的无线电收发机的 \(d\) 值最小。

例如,对于下面三座村庄:

其中,\(|AB|=10,|BC|=20,|AC|=10 \sqrt{5}≈22.36\)

如果没有任何卫星设备或只有 \(1\) 台卫星设备 (\(k=0\)\(k=1\)),则满足条件的最小的 \(d=20\),因为 \(A\)\(B\)\(B\)\(C\) 可以用无线电直接通讯;而 \(A\)\(C\) 可以用 \(B\) 中转实现间接通讯 (即消息从 \(A\) 传到 \(B\),再从 \(B\) 传到 \(C\));

如果有 \(2\) 台卫星设备 (\(k=2\)),则可以把这两台设备分别分配给 \(B\)\(C\) ,这样最小的 \(d\) 可取 \(10\),因为 \(A\)\(B\) 之间可以用无线电直接通讯;\(B\)\(C\) 之间可以用卫星直接通讯;\(A\)\(C\) 可以用 \(B\) 中转实现间接通讯。

如果有 \(3\) 台卫星设备,则 \(A,B,C\) 两两之间都可以直接用卫星通讯,最小的 \(d\) 可取 \(0\)

输入格式
第一行为由空格隔开的两个整数 \(n,k\);

接下来 \(n\) 行,每行两个整数,第 \(i\) 行的 \(x_i,y_i\) 表示第 \(i\) 座村庄的坐标 (\(x_i,y_i\))。

输出格式
一个实数,表示最小的 \(d\) 值,结果保留 \(2\) 位小数。

数据范围

\(1≤n≤500,0≤x,y≤10^4,0≤k≤100\)

输入样例

3 2
10 10
10 0
30 0

输出样例

10.00

二、\(Kruskal\)算法

  • 假设已经确定了参数\(d\)的大小, 那么所有两个 距离\(dis≤d\)的村庄 就可以建立联系, 在图中建立了若干连通块

  • 参数\(d\)的大小联通块的数量 成反比:

    • \(d=0\), 所有村庄间通过卫星相连,共有\(n\)个连通块
    • \(d \geq max(dist_{i,j})\) (\(dist_{i,j}\):图中村庄\(i,j\)之间的距离), 所有顶点连通, 共有\(1\)个连通块

而题目的要求是: 找到满足连通块个数不超过\(k\)个的最小的\(d\)

本质上就是对\(Kruskal\)算法的 魔改 ,考虑\(Kruskal\)的计算过程:

按照边权递增的顺序, 当把两个不连通的顶点连通时, 相当于在图中减少了一个连通块, 在连通块恰好减少到\(k\)时, 对应的边权因为有递增的保证, 所以是满足条件的最小边权。

#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> PII;
#define x first
#define y second

const int N = 510, M = N * N / 2; // C(n,2)
int n, k;

struct Edge {
    int a, b;
    double c;
    const bool operator<(const Edge &t) const {
        return c < t.c;
    }
} edge[M];
int el;

// 每个村庄的坐标
PII q[M];

// 欧几里得距离
double get_dist(PII a, PII b) {
    int x = a.x - b.x, y = a.y - b.y;
    return sqrt(x * x + y * y);
}

// 并查集
int p[N];
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main() {
    cin >> n >> k;                                       // n座村庄,有k台卫星设备
    for (int i = 0; i < n; i++) cin >> q[i].x >> q[i].y; // 村庄坐标

    // 枚举所有点与点之间的边
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; j++)
            edge[el++] = {i, j, get_dist(q[i], q[j])}; // 因为是一个无向图,所以我们取一半就可以了
    // 之所以取一半,其实也是为了配合后面的 cnt--,因为cnt--的现实含义是两个点连接上了,家族数量减少了1个
    // 如果这里不是取一半,那么后面就需要cnt-=2, 哈哈,就问你是不是懵~

    // 边权由小到大排序
    sort(edge, edge + el);

    // 并查集初始化
    for (int i = 0; i < n; i++) p[i] = i;

    int cnt = n; // 剩余的连通块数量
    double res = 0;

    // 原则:长的用卫星,短的用无线电收发机
    // 合并完之后,正好剩下k个连通块,停止,每个连通块上安装卫星即可全面通讯

    // 给原图的节点中n - k个节点生成一棵最小生成树
    for (int i = 0; i < el; i++) { // 枚举每条边
        if (cnt == k) break;       // 剩余点数为k时停止, 在这k个点上建立卫星站
        int a = edge[i].a, b = edge[i].b;
        double c = edge[i].c;
        a = find(a), b = find(b);
        if (a != b) {
            p[a] = b;
            res = c; // 不停的记录参数d的上限
            cnt--;   // 连通块数量-1
        }
    }
    printf("%.2lf\n", res);
    return 0;
}
posted @ 2022-03-25 10:19  糖豆爸爸  阅读(184)  评论(0)    收藏  举报
Live2D