题解:P16549 [ICPC 2026 LAC] Crop Circles
lailai0916 · · 题解
题意简述
给定
其中
解题思路
记第
下面只考虑半径
设某个可行圆的圆心为
接触点不可能位于某个原圆盘的内部,否则它也位于
考虑候选圆在
由于
若一次项系数为
若一次项系数不为
所以,每个接触点都是两个原圆的交点,并且不在任何原圆盘内部。称这样的交点为外露交点。这里的外露同时包含并集外边界和孔洞边界,不能只保留最外层轮廓。
上述异号条件还说明:在保持经过
现在证明最优圆至少有三个不同的接触点。由于所有可行圆都处于
当
- 没有接触点:整个圆周严格位于
U 内部,可以略微增大半径。 - 只有一个接触点:保持经过该点,将圆心沿远离该点的方向略微移动,半径随之增大。
- 只有两个接触点:保持经过这两点,让圆心沿它们的垂直平分线移动。若弦长为
d ,圆心到弦中点的距离为t ,则半径为\sqrt{d^2/4+t^2} 。略微增大|t| 即可增大半径;即使原来t=0 ,仍然可以这样移动。
后两种移动不会破坏接触点附近的覆盖。去掉这些点的小邻域后,剩余部分是严格位于
三种情况都能得到半径更大的可行圆,与最优性矛盾。所以,最优半径若大于
据此,先枚举所有原圆对的交点,删除严格位于其他圆盘内部的点,并合并重复点。之后枚举三个不共线的保留点,求它们的外接圆,只验证半径大于当前答案的候选圆。初始答案取
还需要准确判断一个候选圆的整条圆周是否被覆盖,不能只检查三个确定点,也不能检查固定数量的采样点。
对圆心为
若
若两个圆外离,或者候选圆包住原圆盘但两条圆周不相交,则这个圆盘不能覆盖正长度的候选圆弧。相切产生的孤立点也不能填补任何正长度的空缺,可以忽略。
其余情况下,两圆相交,覆盖的是以
将跨过
代码在区间判定前做了两个必要条件检查:候选圆不能超出全部圆盘的包围盒,上、下、左、右四个极值点也必须被覆盖。它们只用于提前排除明显不合法的候选圆,最终是否覆盖整条圆周仍由角度区间判定。
计算使用 long double。对反余弦的参数截断到
设外露交点数为
这里
从各个
总时间复杂度为
参考代码
#include <bits/stdc++.h>
using namespace std;
using ld=long double;
const int N=45;
const int M=1565;
const ld eps=1e-12;
const ld pi=acosl(-1);
const int dx[4]={1,0,-1,0};
const int dy[4]={0,1,0,-1};
struct point
{
ld x,y;
point operator+(point b){return {x+b.x,y+b.y};}
point operator-(point b){return {x-b.x,y-b.y};}
point operator*(ld k){return {x*k,y*k};}
}v[M];
struct circle
{
point p;
ld r;
}a[N];
int n,cnt;
ld ans,lx,rx,ly,ry;
ld norm(point p){return p.x*p.x+p.y*p.y;}
void add(point p)
{
for(int i=1;i<=n;i++)if(norm(p-a[i].p)<(a[i].r-eps)*(a[i].r-eps))return;
for(int i=1;i<=cnt;i++)if(norm(p-v[i])<eps*eps)return;
cnt++;
v[cnt]=p;
}
bool check(point p,ld r)
{
if(p.x-r<lx-eps||p.x+r>rx+eps||p.y-r<ly-eps||p.y+r>ry+eps)return 0;
for(int i=0;i<4;i++)
{
point q={p.x+r*dx[i],p.y+r*dy[i]};
bool flag=0;
for(int j=1;j<=n;j++)if(norm(q-a[j].p)<=(a[j].r+eps)*(a[j].r+eps)){flag=1;break;}
if(!flag)return 0;
}
pair<ld,ld> seg[2*N];
int cnt=0;
for(int i=1;i<=n;i++)
{
point q=a[i].p-p;
ld d=sqrt(norm(q));
if(d+r<=a[i].r)return 1;
if(d>=r+a[i].r||d<=abs(r-a[i].r))continue;
ld ang=atan2(q.y,q.x);
if(ang<0)ang+=2*pi;
ld len=acos(clamp((d*d+r*r-a[i].r*a[i].r)/(2*d*r),(ld)-1,(ld)1));
ld l=ang-len,r=ang+len;
if(l<0){seg[cnt++]={l+2*pi,2*pi};l=0;}
if(r>2*pi){seg[cnt++]={0,r-2*pi};r=2*pi;}
seg[cnt++]={l,r};
}
sort(seg,seg+cnt);
ld cur=0;
for(int i=0;i<cnt;i++)
{
if(seg[i].first>cur+eps)return 0;
cur=max(cur,seg[i].second);
}
return cur>=2*pi-eps;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>n;
lx=ly=1e9;
rx=ry=-1e9;
for(int i=1;i<=n;i++)
{
cin>>a[i].p.x>>a[i].p.y>>a[i].r;
ans=max(ans,a[i].r);
lx=min(lx,a[i].p.x-a[i].r);
rx=max(rx,a[i].p.x+a[i].r);
ly=min(ly,a[i].p.y-a[i].r);
ry=max(ry,a[i].p.y+a[i].r);
}
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
point q=a[j].p-a[i].p;
ld d=sqrt(norm(q));
if(d>a[i].r+a[j].r||d<abs(a[i].r-a[j].r))continue;
ld x=(a[i].r*a[i].r-a[j].r*a[j].r+d*d)/(2*d);
ld y=sqrt(max((ld)0,a[i].r*a[i].r-x*x));
point p=a[i].p+q*(x/d),o={-q.y*y/d,q.x*y/d};
add(p+o);
add(p-o);
}
}
for(int i=1;i<=cnt;i++)
{
for(int j=i+1;j<=cnt;j++)
{
for(int k=j+1;k<=cnt;k++)
{
point q=v[j]-v[i],o=v[k]-v[i];
ld d=2*(q.x*o.y-q.y*o.x);
if(d==0)continue;
point p=v[i]+point{(norm(q)*o.y-norm(o)*q.y)/d,(q.x*norm(o)-o.x*norm(q))/d};
ld r=sqrt(norm(p-v[i]));
if(r>ans&&check(p,r))ans=r;
}
}
}
cout<<fixed<<setprecision(10)<<ans<<'\n';
return 0;
}