题解 P6947【[ICPC2018 WF]Panda Preserve】
xtx1092515503 · · 题解
首先要把问题转化一下:我们不求 能覆盖所有点的最小半径,而求 覆盖每个点所需最小半径的最大值。显然这两个问题是等价的。
事实上,覆盖一个点所需最小半径,就是这个点到某个顶点的最近距离。
那么这让我们想到 Voronoi 图:关于某个点集
假如我们已经求出所有凸壳。注意任何一个 面积有限 的 不一定简单的(即,其可以有若干相离部分)多边形 到某一关键点的最远点必然位于边界(可能是其中某个部分的边界)上(可以调整,因为向边界移动必然不更劣),进而必然位于顶点(可能是某个部分的顶点)处(因为向左向右两个方向中必然有一个不更劣)。故只要顶点数可承受,我们就可以进行尝试了。
注意到并非凸壳中所有点都可以被选:其必须同时还是题面中给定的简单多边形内部的点。这意味着可选点集事实上是凸壳与给定多边形的交集。这必然是面积有限但不一定简单的多边形。
假如我们已经求出交集,则只需枚举交集的所有顶点,求出其到关键点的距离的最大值即可。
考虑交集的顶点可能有三种类型:凸壳中的顶点;给定多边形的顶点;凸壳中边与给定多边形中边的交点。
第一类可以枚举顶点然后用射线法判定是否在多边形内部解决。
射线法:自一个在简单多边形内部的顶点向外引出一条射线,其必然与该多边形的边界相交奇数次,而在外部的顶点引出的射线必然相交偶数次。
第二类不需要处理,因为多边形的顶点所需的最小半径必然是零(其自身就是集合
第三类可以枚举简单多边形的边,用一些方法(比如说,二分/三分)在凸壳上找出交点,然后尝试更新答案。
考虑分析复杂度。计算第一类顶点需要的复杂度为
但是,只要注意到一个性质,即 V 图中 点数和边数都是关于
最后还有一个问题,就是如何求出 V 图。假如只有两个点,则考虑这两点的中垂线(垂直平分线),其左侧是到左方点更近的点集,右侧是到右方点更近的点集。对于一个点,取所有其它点求中垂线,则靠近其一侧的所有半平面的交集即为其对应的凸壳。
直接对每个点各跑一遍半平面交,复杂度平方对数。
需要注意的是,本题的瓶颈 完全在半平面交的排序。因此尽量优化你半平面交的比较。我一开始的写法一次比较傻傻地做了 atan2,然后因为常数过大在 CF 上也光荣 TLE。后来预处理出 atan2 的结果,然后就卡过去了。
代码:
#include<bits/stdc++.h>
using namespace std;
const double eps=1e-8;
const double pi=acos(-1);
int cmp(double x){if(x>eps)return 1;if(x<-eps)return -1;return 0;}
double res;
struct Vector{
double x,y;
Vector(){}
Vector(double X,double Y){x=X,y=Y;}
friend Vector operator +(const Vector &u,const Vector &v){return Vector(u.x+v.x,u.y+v.y);}
friend Vector operator -(const Vector &u,const Vector &v){return Vector(u.x-v.x,u.y-v.y);}
friend Vector operator *(const Vector &u,const double &v){return Vector(u.x*v,u.y*v);}
friend Vector operator /(const Vector &u,const double &v){return Vector(u.x/v,u.y/v);}
friend double operator &(const Vector &u,const Vector &v){return u.x*v.y-u.y*v.x;}
friend double operator |(const Vector &u,const Vector &v){return u.x*v.x+u.y*v.y;}
friend bool operator ==(const Vector &u,const Vector &v){return cmp(u.x-v.x)==0&&cmp(u.y-v.y)==0;}
double operator ~()const{return sqrt(x*x+y*y);}//the modulo of a vector
double operator !()const{return atan2(y,x);}//the angle of a vector
void read(){scanf("%lf%lf",&x,&y);}
void print()const{printf("(%lf,%lf)",x,y);}
Vector turn(){return Vector(y,-x);}
friend bool operator<(const Vector&u,const Vector&v){
if(cmp(u.x-v.x)==0)return cmp(u.y-v.y)==-1;
return cmp(u.x-v.x)==-1;
}
}p[2010],q[2010];
typedef Vector Point;
struct Line{
Point x,y;
Vector z;double ang;
Line(){}
Line(Point X,Point Y){x=X,y=Y,z=Y-X,ang=!z;}
friend bool operator<(const Line&u,const Line&v){
if(cmp(u.ang-v.ang))return u.ang<v.ang;
return cmp(u.z&(v.y-u.x))==-1;
}
friend bool operator/(const Line&u,const Line&v){return cmp(u.z&v.z)==0;}
friend bool operator|(const Line&u,const Line&v){return cmp(u.ang-v.ang)==0;}
friend bool operator&(const Line&u,const Point&v){return cmp((v-u.x)&u.z)!=1;}
friend bool operator%(const Line&u,const Point&v){return cmp((u.x-v)|(u.y-v))!=1;}
friend Point operator&(const Line&u,const Line&v){return u.x+u.z*((v.z&(u.x-v.x))/(u.z&v.z));}
}l[2010],r[2010];
typedef Line Segment;
int n,L,R,m;
set<Point>st;
bool inside(Point P){
if(st.find(P)!=st.end())return false;
st.insert(P);
static default_random_engine rnd(17680321);
static uniform_real_distribution<double>urd(-pi,pi);
while(true){
double ang=urd(rnd);
Point Q(P.x+cos(ang),P.y+sin(ang));
Line X(P,Q);
int num=0;
for(int i=0;i<n;i++){
Point A=p[i],B=p[(i+1)%n];
Line Y(A,B);
Point C=Y&X;
if(C==B||C==A){num=-1;break;}
if(!(Y%C)||(Segment(Q,C)%P&&!(P==C)))continue;
num++;
}
if(num!=-1)return num&1;
}
}
void solve(int _){
m=0;
for(int i=0;i<n;i++)if(i!=_){
Point P=(p[i]+p[_])/2;
Point Q=P+(p[_]-p[i]).turn();
// P.print(),Q.print();puts("");
l[m++]=Line(P,Q);
}
l[m++]=Line(Point(-2e4,-2e4),Point(2e4,-2e4));
l[m++]=Line(Point(2e4,-2e4),Point(2e4,2e4));
l[m++]=Line(Point(2e4,2e4),Point(-2e4,2e4));
l[m++]=Line(Point(-2e4,2e4),Point(-2e4,-2e4));
sort(l,l+m),r[L=R=0]=l[0];
for(int i=1;;i++){
while(i<m&&l[i]|l[i-1])i++;
if(i==m)break;
if(L<R&&((r[L]/r[L+1])||(r[R]/r[R-1])))return;
while(L<R&&!(l[i]&q[R-1]))R--;
while(L<R&&!(l[i]&q[L]))L++;
r[++R]=l[i];if(L<R)q[R-1]=r[R]&r[R-1];
}
while(L<R&&!(r[L]&q[R-1]))R--;
while(L<R&&!(r[R]&q[L]))L++;
if(R-L<=1)return;
q[R]=r[L]&r[R];
// printf("%d:\n",_);
// for(int i=L;i<=R;i++)q[i].print();puts("");
for(int i=L;i<=R;i++)if(inside(q[i]))res=max(res,~(q[i]-p[_]));
for(int i=L;i<=R;i++)for(int j=0;j<n;j++){
Segment s(p[j],p[(j+1)%n]),t(q[i],q[i==R?L:i+1]);
if(s/t)continue;
Point P=s&t;
if(s%P&&t%P)res=max(res,~(P-p[_]));
}
}
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++)p[i].read();
for(int i=0;i<n;i++)solve(i);
printf("%lf\n",res);
return 0;
}