P4047 [JSOI2010]部落划分
题目描述
聪聪研究发现,荒岛野人总是过着群居的生活,但是,并不是整个荒岛上的所有野人都属于同一个部落,野人们总是拉帮结派形成属于自己的部落,不同的部落之间则经常发生争斗。只是,这一切都成为谜团了——聪聪根本就不知道部落究竟是如何分布的。
不过好消息是,聪聪得到了一份荒岛的地图。地图上标注了 n 个野人居住的地点(可以看作是平面上的坐标)。我们知道,同一个部落的野人总是生活在附近。我们把两个部落的距离,定义为部落中距离最近的那两个居住点的距离。聪聪还获得了一个有意义的信息——这些野人总共被分为了 k 个部落!这真是个好消息。聪聪希望从这些信息里挖掘出所有部落的详细信息。他正在尝试这样一种算法:
对于任意一种部落划分的方法,都能够求出两个部落之间的距离,聪聪希望求出一种部落划分的方法,使靠得最近的两个部落尽可能远离。
思路
依次把两个最近的人划分到一个部落中去(若其中一个或两个人还属于其他部落,那么这两个部落合并),当最后剩下单独的k个人和部落的时候,这时候这其中的,相距最近的两个部落/部落和人的距离就是最重要的答案。
仔细观察,其实这就是一个最小生成树,建n-k条边的最小生成树,第n-k+1条边就是要的结果。
AC代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
const int Maxn = 1005;
struct EDGE {
int u, v;
double w;
EDGE(){}
EDGE(int u, int v, double w):u(u), v(v), w(w){}
bool operator < (const EDGE &x) const {
return w < x.w;
}
} e[Maxn * Maxn];
double a[Maxn][2];
int tot = 0, father[Maxn];
int Find(int x) {
return father[x] == x ? x : father[x] = Find(father[x]);
}
void Union(int u, int v) {
int ru = Find(u);
int rv = Find(v);
if (ru != rv) {
father[ru] = rv;
}
}
double getDis(int u, int v) {
return sqrt(pow(a[u][0] - a[v][0], 2.0) + pow(a[u][1] - a[v][1], 2.0));
}
double Kruskal(int nv, int k) {
for (int i = 0; i <= nv; i++) {
father[i] = i;
}
int t = 0;
double ans = 0;
for (int i = 0; i < tot; i++) {
int u = e[i].u;
int v = e[i].v;
if (Find(u) != Find(v)) {
Union(u, v);
t++;
if (t == nv - k + 1) {
ans = e[i].w;
}
}
}
return ans;
}
void solve() {
int n, k;
scanf("%d %d", &n, &k);
for (int i = 0; i < n; i++) {
scanf("%lf %lf", &a[i][0], &a[i][1]);
}
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
e[tot++] = EDGE(i, j, getDis(i, j));
}
}
std::sort(e, e + tot);
double ans = Kruskal(n, k);
printf("%.2f", ans);
}
int main() {
solve();
return 0;
}