【Codeforces 744D】Hongcow Draws a Circle
二分答案,简单计算几何
题目大意:
给定 $n$ 个红点和 $m$ 个蓝点,保证都是整点且不会有重合。要求找一个半径最大的圆,使得至少包含一个红点,但不包含蓝点(恰在圆上的点可以视为包含也可以视为不包含)。求最大的半径。无穷大输出 $-1$。
题解:
设答案为 $ans$,那么半径小于 $ans$ 的圆都是合法的答案。所以可以二分答案。
显然会有一个蓝点恰在边界上。如果没有,那么可以扩大半径直到有蓝点在边界上,这个显然更优。
那么考虑枚举边界上的蓝点,二分答案。
在 check 一个答案的时候,对每个点,计算出包含它的圆的圆心所在的极角范围(通过一些计算几何,三角函数就可以求出),然后扫描线即可,看是否存在一个极角,包含至少一个红点,不包含蓝点。时间复杂度 $O(n\log n)$(排序)。
这样做有一个很大的问题:对于一个半径 $r$,不存在一个半径为 $r$ 的点满足当前蓝点在边界上且包含红点。即没有合法的红点。但答案可能比当前 $r$ 更优。
我们先枚举红点,二分出把一个红点放在圆边界上的最大答案,这个直接二分没问题。
然后用当前答案作为二分蓝点时的最小答案,二分蓝点的答案即可。
为什么这样就不会出问题了呢?设红点答案半径为 $r$,对于一个蓝点,存在一个红点,使得包含这个蓝点和红点的最小圆的半径为 $r’$,那么若这个圆合法,则 $r\geq r’$。如果 $r<r’$,说明这个最小的圆里面有蓝点,本身不合法。
这样就避免了范围内不存在红点的情况。
时间复杂度 $O(n^2\log n\log V)$。虽然 $n$ 只有 $1000$,但是要涉及实数运算,而且二分的是实数,所以效率比较低。
有一个剪枝就是,新枚举到一个点,先 check 当前答案,如果当前答案不行,就直接跳过这个点。我们令每个点在边界上的最大答案分别为 $a_1,a_2,\dots,a_n$。上面的剪枝,当 $a$ 为升序时就会无效。
实际上,加上这个剪枝后,二分答案的次数,就是 $a$ 这个数列的最长上升子序列长度。而一个随机排列的最长上升子序列长度,是 $\log n$ 的。
所以我们对这些点random_shuffle
一遍,然后加上这个剪枝即可。
期望复杂度 $O(n\log n\log V+n^2\log n)$。
Code:
#include<cstdio>
#include<algorithm>
#include<ctime>
#include<cmath>
#include<cstdlib>
using namespace std;
const int N=4005;
const double eps=1e-9,pi=acos(-1);
struct point{
int x,y;
}g[N];
int n,m;
double ans=0;
inline double dist(point a,point b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
struct node{
double rd;int dlt;
inline bool operator<(const node&rhs)const{return(fabs(rd-rhs.rd)>eps?rd<rhs.rd:dlt<rhs.dlt);}
}sta[N*2];
bool check(int id,double r){
point t=g[id];
int top=0;
if(id<=n)
sta[++top]=(node){0,1},sta[++top]=(node){2*pi,-1};
else
for(int i=1;i<=n;++i){
const double d=dist(t,g[i]);
if(2*r+eps<d)continue;
double deg=acos(d/2/r);
double rgd=asin((g[i].y-t.y)/d);
if(g[i].x<t.x)rgd=pi-rgd;
double L=rgd-deg,R=rgd+deg;
if(L<0&&R<=0)L+=2*pi,R+=2*pi;
if(L<0)sta[++top]=(node){L+2*pi,1},sta[++top]=(node){2*pi,-1},L=0;
sta[++top]=(node){L,1},sta[++top]=(node){R,-1};
}
for(int i=n+1;i<=n+m;++i)
if(id!=i){
const double d=dist(t,g[i]);
if(2*r-eps<d)continue;
double deg=acos(d/2/r);
double rgd=asin((g[i].y-t.y)/d);
if(g[i].x<t.x)rgd=pi-rgd;
double L=rgd-deg,R=rgd+deg;
if(L<0&&R<=0)L+=2*pi,R+=2*pi;
if(L<0)sta[++top]=(node){L+2*pi,-10000},sta[++top]=(node){2*pi,10000},L=0;
sta[++top]=(node){L,-10000},sta[++top]=(node){R,10000};
}
sort(sta+1,sta+top+1);
sta[top+1].rd=123456;
int now=id<=n;
for(int i=1;i<=top;++i){
if(i>1&&now>0&&sta[i+1].rd-sta[i].rd>eps)return 1;
now+=sta[i].dlt;
}
return 0;
}
void work(){
for(int i=1;i<=n+m;++i)if(check(i,ans)){
int T=0;
double l=ans,r=1e9;
while(r-l>=eps){
double mid=(l+r)/2;
if(check(i,mid))l=ans=mid;else r=mid;
if(++T==128)break;
}
}
}
int main(){
srand(time(0));
scanf("%d%d",&n,&m);
for(int i=1;i<=n+m;++i)scanf("%d%d",&g[i].x,&g[i].y);
random_shuffle(g+1,g+n+1),random_shuffle(g+n+1,g+n+m+1);
work();
if(ans<=0.9e9)printf("%.11f\n",ans);else puts("-1");
return 0;
}