Solution 1:
二分答案加验证
并查集维护分成的部落
小于答案的边必须分到同一个并查集里,最后并查集的数目如果小于k,那么答案偏小
这里有一个问题
二分时,如果把各个点之间的距离排一遍序,二分这个数组的话,会wa
原因是,举个栗子:
比如说各点的距离里面,有一段区间是:[11.2145,12.4000],如果直接二分这个区间,在验证到答案11.2145时,发现11.2145符合答案要求,于是去找更大的答案,这时会二分到12.40000甚至比它更大的。但是,题目要求精确到小数点后面两位数字,也就是说,如果11.2169(比11.2145大但是不超过11.2145的数字)是符合答案的,那就错过了这个答案。实际上这组数据的最后答案就是11.21,如果二分数组的话,答案是12.40.
所以对于这种浮点数的精度问题,还是精确一点好
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=1000+10;
int n,k,cnt,tot1,tot2,tot;
double ans;
int fa[maxn],num[maxn];
double d[maxn<<1],dis[maxn][maxn];
struct hh
{
int x,y;
}e[maxn];
int find(int x)
{
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
bool check(double x)
{
tot=n;
for(int i=1;i<=n;++i) fa[i]=i;
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
{
int f1=find(i),f2=find(j);
if(dis[i][j]<=x)
{
if(f1!=f2)
{
tot--;
fa[f1]=f2;
}
}
}
if(tot<k) return false;
return true;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i) scanf("%d%d",&e[i].x,&e[i].y);
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
{
int x1=e[i].x,y1=e[i].y,x2=e[j].x,y2=e[j].y;
dis[i][j]=dis[j][i]=(double)sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
double l=0,r=10100;
while(r-l>0.0001)
{
double mid=(l+r)/2.0;
if(check(mid)) l=mid;
else r=mid;
}
if(check(r)) ans=r;
else ans=l;
printf("%.2lf",ans);
return 0;
}
Solution 2:
最小生成树
懒得写了,上题解:
最小生成树——首先将任意两点间的距离处理出来。在一开始时,每个点各为一个集合,在不连边的情况下,那么答案一定是所有的边中最小的边。由于已经推出的贪心的思想,我们连接不在同一个集合内的两点就相当于删去这两个点所在集合内部的任意两点间的边。所以我们把这条最小的边连接,此时答案,又是所有剩余的边中最小的边,然后我们再进行连接(shan chu)……。那么显然——答案一定在最小生成树中。
所以我们可以建立一棵最小生成树,来保证每次连边都是不在同一个集合内的两点且长度为当前最小,直到连接了n-k+1条边。
为什么是n-k+1条??
n-k是为了保证能够建成k个集合(已知n-1条边是一棵树,若删去一条边–>n-2是两棵树……那么n-k就是k棵树啦)
+1的原因是为了保证当前求的这一条边是集合外的边,并不是连接的意思。
这样第n-k+1条边的长度就是答案啦