[JSOJ2010]部落划分
题目描述
聪聪研究发现,荒岛野人总是过着群居的生活,但是,并不是整个荒岛上的所有野人都属于同一个部落,野人们总是拉帮结派形成属于自己的部落,不同的部落之间则经常发生争斗。只是,这一切都成为谜团了——聪聪根本就不知道部落究竟是如何分布的。
不过好消息是,聪聪得到了一份荒岛的地图。地图上标注了 nn 个野人居住的地点(可以看作是平面上的坐标)。我们知道,同一个部落的野人总是生活在附近。我们把两个部落的距离,定义为部落中距离最近的那两个居住点的距离。聪聪还获得了一个有意义的信息——这些野人总共被分为了 kk 个部落!这真是个好消息。聪聪希望从这些信息里挖掘出所有部落的详细信息。他正在尝试这样一种算法:
对于任意一种部落划分的方法,都能够求出两个部落之间的距离,聪聪希望求出一种部落划分的方法,使靠得最近的两个部落尽可能远离。
例如,下面的左图表示了一个好的划分,而右图则不是。请你编程帮助聪聪解决这个难题。
输入格式
输入文件第一行包含两个整数 nn 和 kk,分别代表了野人居住点的数量和部落的数量。
接下来 nn 行,每行包含两个整数 xx,yy,描述了一个居住点的坐标。
输出格式
输出一行一个实数,为最优划分时,最近的两个部落的距离,精确到小数点后两位。
输入输出样例
输入 #1
4 2
0 0
0 1
1 1
1 0
输出 #1
1.00
输入 #2
9 3
2 2
2 3
3 2
3 3
3 5
3 6
4 6
6 2
6 3
输出 #2
2.00
说明/提示
数据规模与约定
对于100%的数据,保证2 ≤ \leq ≤ k ≤ \leq ≤n ≤ \leq ≤ 1 0 3 10^3 103,0 ≤ \leq ≤ x,y ≤ \leq ≤ 1 0 4 10^4 104。
解题思路
为了使最近的两个部落最远(目的是尽量远),所以我们可以不断的把不在一个部落且当前距离最短的两个居住点。发现可以用并查集来维护,最后有n-k+1条边,又发现这其实是一道最小生成树的题。
按照边权从小到大的排列,刚开始n个居住点看作是n个部落,每次选取距离最小的两个抱团,部落数–,直到部落数=期待值,此时下一个部落的距离就是最短距离。
数据规模为1000,所以可以放心大胆的Code;
Code
#include<bits/stdc++.h>
using namespace std;
const int maxn=1010;
int F[maxn],siz[maxn],cnt;
double x[maxn],y[maxn];
struct Edge{
double x,y;
double weight;
}Ed[maxn*maxn];
bool cmp(Edge a,Edge b){return a.weight<b.weight;}
double dis(int a,int b){
return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));
}
void Init_Set(){
for(int i=0;i<maxn;i++){
siz[i]=0; F[i]=i;
}
}
int Find(int x){
if(F[x]==x) return x;
return F[x]=Find(F[x]);
}
void Union_Set(int x,int y){
if((x=Find(x))==(y=Find(y))) return;
if(siz[x]<siz[y]) F[x]=y;
else if(siz[x]>siz[y]) F[y]=x;
else{
F[x]=y; siz[y]++;
}
}
int main(){
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>x[i]>>y[i];
}
cnt=0;
for(int i=1;i<n;i++){
for(int j=i+1;j<=n;j++){
Ed[++cnt].weight=dis(i,j);
Ed[cnt].x=i;
Ed[cnt].y=j;
}
}
sort(Ed+1,Ed+cnt+1,cmp);
int t=n;
double ans=0;
Init_Set();
for(int i=1;i<=cnt;i++){
if(t<=k){
for(int j=i;j<=cnt;j++){
if(Find(Ed[j].x)!=Find(Ed[j].y)){
ans=Ed[j].weight;
break;
}
}
break;
}
if(Find(Ed[i].x)!=Find(Ed[i].y)){
Union_Set(Ed[i].x,Ed[i].y);
t--;
}
}
printf("%.2f\n",ans);
return 0;
}