题意:
给出n个半径一致的圆,画一个圆至少完全包含其中的m个,求最小半径。
题解:
可化简为,n个点,求覆盖掉至少m个的最小的圆
O(n^2logn *logR)的解法:
二分半径,然后判断这个半径下能覆盖的最多的点,若小于要求,半径变大,若大于要求,半径缩小。
如何得知一个半径下能覆盖的最多的点?
设半径为r
我们枚举每一个点,表示一定要包含的点,我们在包含住这个点的前提下尽量包含更多的点。
找出所有的与枚举的点距离小于等于2*r的点,以这些点为圆心画半径为r的圆会与枚举的点画的圆有交点。
交点与枚举的点之间形成一段角度,其含义是当圆心在那个角度的范围内画圆时,可以同时包含2个点。
那我们要找的是一个角度,它被最多的上文提到的“一段角度”覆盖。
找的方法是在端点标记1,-1,排序后求前缀和。
代码:
#include<bits/stdc++.h>
#define N 1010
#define mod 1000000007
#define INF 1e17
#define eps 1e-8
#define pi 3.141592653589793
#define LL unsigned long long
#define pb push_back
#define cl clear
#define si size
#define lb lowwer_bound
#define mem(x) memset(x,0,sizeof x)
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;
int dcmp(double x){if (fabs(x)<eps)return 0;else return x<0?-1:1;}
struct Point
{
double x,y;
Point(double x=0,double y=0):x(x),y(y){ }
void read(){scanf("%lf%lf",&x,&y);}
}a[N];
double dis(Point a,Point b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
struct cc
{
double x; int fg;
bool operator < (const cc z)const
{
return dcmp(x-z.x)<=0 || dcmp(x-z.x)==0 && fg>z.fg;
}
}v[N];
int check(double r,int n)
{
int f=0;
for (int i=1;i<=n;i++)
{
int res=0,tot=0;
for (int j=1;j<=n;j++)
{
double R=dis(a[i],a[j]);
if (dcmp(R-r*2)>0) continue;
if (dcmp(R)==0) res++;else
{
double ang=acos(max(-1.0,min(1.0,R*0.5/r)));
double A=atan2(a[j].y-a[i].y,a[j].x-a[i].x);
if (dcmp(A)<0) A+=pi*2;
A+=pi*2;
v[++tot].x=A-ang; v[tot].fg=1;
v[++tot].x=A+ang; v[tot].fg=-1;
}
}
sort(v+1,v+tot+1);
f=max(f,res);
for (int i=1;i<=tot;i++)
{
res+=v[i].fg;
f=max(f,res);
}
}
return f;
}
int main()
{
int T;
sc(T);
while(T--)
{
int n,m; double R;
scc(n,m); for (int i=1;i<=n;i++) a[i].read(); scanf("%lf",&R);
if (m>n)
{
puts("The cake is a lie.");continue;
}
double l=0,r=1e4;
while(r-l>1e-6)
{
double t=(l+r)*0.5;
if (check(t,n)>=m)r=t;else l=t;
}
printf("%.4f\n",r+R);
}
return 0;
}