题解 P4047 [JSOI2010]部落划分
题意简述
给定 \(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;
}