思路
这题的前身可以认为是POJ1981,POJ1981讲的是平面上有n个点单位圆最多能覆盖几个点,他是有O(n2logn)的做法的,那我们回到这一题,我们先不考虑整圆,只考虑圆心,如果我们求出来的大圆能包含s个圆心那么我们让大圆的半径增加R,那就可以覆盖整个圆了,所以现在的问题就是如何快速的求出对一个大圆的半径有最多能覆盖多少个点。
算法思路
朴素算法:由限制条件可知,最后目标圆圆心一定可由某两点唯一确定(即目标圆的圆弧上一定恰有两个平面点集中的点)。具体实现:C(N,2)枚举任意两圆,构造可行圆,枚举N个点,判断是否在圆上或者圆内(即是否被覆盖)。复杂度O(N^3)。我所知的标准做法:O(n^2*logn)的圆上交点扫描法。
转化为圆上的弧最多被覆盖几次的问题。
直觉可解,圆上某个弧如果被某个圆覆盖过,那么我们把目标圆圆心放在该段弧上时,目标圆一定能把该弧所在圆圆心覆盖(即平面点集中的某点)。
所以,被覆盖次数最多的弧的被覆盖次数即为目标圆(最优)最多能够覆盖的点数。
具体做法:
依旧是C(N,2)枚举任意两点,以BA为例,此时i=1,j=0。
求取BA的极角theta,利用atan2(因变量范围(-pi,pi])。
为方便极角排序,对于负角要+2pi。
B、A构造的圆交于X1,X4两点,求取X1B与BA的夹角和X4B与AB的夹角(相同)。
Phi ,acos(D/2.0/R),D=dis(A,B)。
以上可得出两圆交点在当前圆上对于圆心的极角。
为方便极角排序,每个极角均加2pi,使域扩张到[0,4pi],从而防止了跨0角的出现。
alpha[t].value=theta – phi + 2pi,alpha[t].flag = 1(弧的进入端)
alpha[t+1].value = theta + phi + 2pi,alpha[t+1].flag = false(弧的退出端)。
接下来,对于每个圆上的t(偶数)个极角进行排序,再顺序扫描,动态更新最大值。
for(i,0,t)
if(alpha[i].flag)
sum++
else
sum–
if(sum > ans)
ans = sum
最后输出答案时,输出ans+1即可。(因为要算上弧所在圆自身)
对应算法的模板
#include <bits/stdc++.h>
using namespace std;
#define Mn 1000
const double eps = 1e-9;
const double pi = acos(-1.0);
const int maxn=1e5+9;
#define sqr(x) ((x) * (x))
struct point
{
double x,y;
void read()
{
scanf("%lf%lf",&x,&y);
}
void print()
{
printf("%lf%lf\n",x,y);
}
double friend dis(const point &a,const point &b)
{
return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y));
}
} p[Mn + 5];
struct alpha
{
double v;
bool flag;
bool friend operator <(const alpha &a,const alpha &b)
{
return a.v < b.v;
}
} alp[Mn * Mn + 5];
int solve(int n,double R)//半径为R的圆最多能覆盖多少个点
{
int MAX = 0;
for(int i = 0; i < n; i++)
{
int t = 0;
for(int j = 0; j < n; j++)
{
if(i == j)
continue;
double theta,phi,D;
D = dis(p[i],p[j]);
if(D > 2.0 * R)
continue;
theta = atan2(p[j].y - p[i].y,p[j].x - p[i].x);
if(theta < 0)
theta += 2 * pi;
phi = acos(D / (2.0 * R));
alp[t].v = theta - phi + 2 * pi;
alp[t].flag = true;
alp[t + 1].v = theta + phi + 2 * pi;
alp[t + 1].flag = false;
t += 2;
}
sort(alp,alp + t);
int sum = 0;
for(int j = 0; j < t; j++)
{
if(alp[j].flag)
sum ++;
else
sum --;
if(sum > MAX)
MAX = sum;
}
}
return MAX+1;
}
题目代码
#include <bits/stdc++.h>
using namespace std;
#define Mn 1000
const double eps = 1e-9;
const double pi = acos(-1.0);
const int maxn=1e5+9;
#define sqr(x) ((x) * (x))
struct point
{
double x,y;
void read()
{
scanf("%lf%lf",&x,&y);
}
void print()
{
printf("%lf%lf\n",x,y);
}
double friend dis(const point &a,const point &b)
{
return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y));
}
} p[Mn + 5];
struct alpha
{
double v;
bool flag;
bool friend operator <(const alpha &a,const alpha &b)
{
return a.v < b.v;
}
} alp[Mn * Mn + 5];
int solve(int n,double R)//半径为R的圆最多能覆盖多少个点
{
int MAX = 0;
for(int i = 0; i < n; i++)
{
int t = 0;
for(int j = 0; j < n; j++)
{
if(i == j)
continue;
double theta,phi,D;
D = dis(p[i],p[j]);
if(D > 2.0 * R)
continue;
theta = atan2(p[j].y - p[i].y,p[j].x - p[i].x);
if(theta < 0)
theta += 2 * pi;
phi = acos(D / (2.0 * R));
alp[t].v = theta - phi + 2 * pi;
alp[t].flag = true;
alp[t + 1].v = theta + phi + 2 * pi;
alp[t + 1].flag = false;
t += 2;
}
sort(alp,alp + t);
int sum = 0;
for(int j = 0; j < t; j++)
{
if(alp[j].flag)
sum ++;
else
sum --;
if(sum > MAX)
MAX = sum;
}
}
return MAX+1;
}
int n,s;
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int T;
cin>>T;
while(T--)
{
scanf("%d%d",&n,&s);
for(int i=0;i<n;i++)
{
p[i].read();
}
double d;
scanf("%lf",&d);
if(n<s)
{
printf("The cake is a lie.\n");
}
else
{
double l=0,r=9e8+7;
while(r-l>eps)
{
double mid=(l+r)*0.5;
int tmp=solve(n,mid);
if(tmp>=s)//注意二分的做法,因为要求最小半径,所以最后取l,并且当tmp>=s时,r=mid,若求最大值,则反过来.
{
r=mid;
}
else
{
l=mid;
}
}
printf("%.4lf\n",l+d);
}
}
return 0;
}