题解 P4047 [JSOI2010]部落划分

题意简述

Link

给定 \(n\) 个点,每个点有两个坐标 \((x_i,y_i)\) ,两点间的距离是欧几里得距离,请你把这 \(n\) 个点划分成 \(k\) 个集合,使得这 \(k\) 个集合距离的最小值最大。

这里两个集合的距离定义为:

\[\operatorname{dist(A,B)}=\min_{x \in A ,y \in b} \operatorname{dist}(a,b) \]

其中 \(\operatorname{dist}(A,B)\) 表示集合 \(A,B\) 的距离, \(\operatorname{dist}(a,b)\) 表示点 \(a\) 和点 \(b\) 的距离。

通俗来讲就是从两个集合中各选出一个点之间的距离的最小值。

Solution

把题目转换一下,其实就是从中选出一些边,使得这些边刚好把 \(n\) 个点分成了 \(k\) 个连通块。然后求剩下的没有选上的边的权值的最小值。

考虑按距离从小到大选边(因为只有选了最小值才会改变答案),设当前边的两个段点分别为 \(u,v\) ,分两种情况讨论:

  • \(u,v\) 已经联通,此时如果连接 \((u,v)\) ,对原来的图的连通性没有影响,并且答案变大了,一定比原来优,所以一定会选上。
  • \(u,v\) 不连通,此时若连上这一条边,连通块数量会少 \(1\) ,那么如果说当前的连通块数量已经是 \(k\) 了,就不能继续连接了。否则因为如果不连上当前的这一条边,答案不可能会发生改变,所以当前边也一定连上。

上面其实是 Kruskal 算法进行到一半,可以证明,这也是正确的。

所以我们只需要建出所有边,按照边权从小到大排序,然后维护一个连通块数量(最开始为 \(n\) ),按照 Kruskal 做就可以。

注意:如果当前连通块数量已经是 \(k\) 了,那么剩下的边权最小的边如果连接的两个点是联通的,那么下面这一条边也必须选上。

代码如下:

#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <iostream>
#include <cmath>
using namespace std;
inline int read() {
    int num = 0 ,f = 1; char c = getchar();
    while (!isdigit(c)) f = c == '-' ? -1 : f ,c = getchar();
    while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar();
    return num * f;
}
const int N = 1e3 + 5 ,M = 5e5 + 5 ,INF = 0x3f3f3f3f;
inline double getdist(double x1 ,double y1 ,double x2 ,double y2) {
    return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}
struct edge {
    int u ,v; double w;
    edge (int u = 0 ,int v = 0 ,double w = 0) : u(u) ,v(v) ,w(w) {}
    friend bool operator < (edge a ,edge b) {return a.w < b.w;} 
}e[M];
struct unionFind {
    int bin[N];
    inline void init(int n) {for (int i = 1; i <= n; i++) bin[i] = i;}
    inline int find(int x) {return bin[x] == x ? x : bin[x] = find(bin[x]);}
    inline void merge(int x ,int y) {bin[find(y)] = find(x);}
}u;
struct node {
    int x ,y;
    node (int x = 0 ,int y = 0) : x(x) ,y(y) {}
}a[N];
int n ,m ,k;
signed main() {
    n = read(); k = read(); 
    for (int i = 1; i <= n; i++)
        a[i].x = read() ,a[i].y = read();
    for (int i = 1; i <= n; i++)
        for (int j = i + 1; j <= n; j++)
            e[++m] = edge(i ,j ,getdist(a[i].x ,a[i].y ,a[j].x ,a[j].y));
    sort(e + 1 ,e + m + 1);
    u.init(n);
    int cnt = n;
    for (int i = 1; i <= m && cnt >= k; i++) {
        int x = e[i].u ,y = e[i].v; double w = e[i].w;
        x = u.find(x); y = u.find(y);
        if (x == y) continue;
        if (cnt == k) return printf("%.2lf\n" ,w) ,0;
        cnt--;
        u.merge(x ,y);
    }
    return 0;
}
posted @ 2021-04-29 13:10  recollector  阅读(54)  评论(0编辑  收藏  举报