题解:P16629 [GKS 2017 #E] Blackhole
lailai0916 · · 题解
题意简述
空间中有三个黑洞。需要放置恰好三个半径相同的球,使每个黑洞都被覆盖,且三个球的并集连通。求球半径的最小值。
解题思路
三个黑洞必定位于同一平面。把任意球心正交投影到这个平面,球心到黑洞的距离不会增大,球心之间的距离也不会增大。因此,覆盖关系与连通关系都不会被破坏。原问题可以转化为平面上的三个等半径圆。
先固定半径
第一类是三个圆各自负责一个黑洞。三个圆的相交图必须连通,而三个顶点的连通图中一定存在一个顶点与另外两个顶点连通。设这个中间圆负责
因为中间圆覆盖
对于另一个黑洞
必要性来自三角不等式。反过来,若
因此,枚举中间圆负责的黑洞后,只需判断三个圆盘是否存在公共点:
第二类是某个圆负责至少两个黑洞。设它同时覆盖
从
这个条件也充分。沿线段
至此,问题变为检查以下六组权值:
对一组权值
三个圆盘若有公共点,则公共区域中一定存在某个圆心,或某两个圆的边界交点。否则,公共区域的边界没有由两段圆弧形成的顶点,只能等于某个完整圆盘。这个圆盘的圆心便属于另外两个圆盘,与假设矛盾。
圆心候选可直接用两点距离判断。对于边界交点,也无须显式建立整个平面坐标系。设检查第
同一坐标系下,可把
两个交点中,
就找到了三个圆盘的公共点。三个黑洞互不重合,因此公式中的
可行性随
参考代码
#include <bits/stdc++.h>
using namespace std;
using ld=long double;
const ld eps=1e-12;
ld d[3][3];
ld dis(const ld a[],const ld b[])
{
ld res=0;
for(int i=0;i<3;i++)res+=(a[i]-b[i])*(a[i]-b[i]);
return sqrt(res);
}
bool fit(int u,const ld a[])
{
for(int i=0;i<3;i++)if(d[u][i]>a[i]+eps)return 0;
return 1;
}
bool cross(int i,int j,int k,const ld a[])
{
ld len=d[i][j];
if(len>a[i]+a[j]+eps||len<abs(a[i]-a[j])-eps)return 0;
ld x,y,h,z;
x=(a[i]*a[i]-a[j]*a[j]+len*len)/(2*len);
y=(d[i][k]*d[i][k]-d[j][k]*d[j][k]+len*len)/(2*len);
h=sqrt(max(0.0L,a[i]*a[i]-x*x));
z=sqrt(max(0.0L,d[i][k]*d[i][k]-y*y));
return hypot(x-y,h-z)<=a[k]+eps;
}
bool check(ld r,const int w[])
{
ld a[3];
for(int i=0;i<3;i++)a[i]=r*w[i];
for(int i=0;i<3;i++)if(fit(i,a))return 1;
for(int i=0;i<3;i++)for(int j=i+1;j<3;j++)if(cross(i,j,3-i-j,a))return 1;
return 0;
}
bool check(ld r)
{
const int w[6][3]={{1,3,3},{3,1,3},{3,3,1},{5,1,1},{1,5,1},{1,1,5}};
for(int i=0;i<6;i++)if(check(r,w[i]))return 1;
return 0;
}
ld solve()
{
ld p[3][3];
for(int i=0;i<3;i++)for(int j=0;j<3;j++)cin>>p[i][j];
for(int i=0;i<3;i++)for(int j=i+1;j<3;j++)d[i][j]=d[j][i]=dis(p[i],p[j]);
ld l=0,r=4000;
for(int i=0;i<100;i++)
{
ld mid=(l+r)/2;
if(check(mid))r=mid;
else l=mid;
}
return r;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
cout<<fixed<<setprecision(10);
int _=0;
while(T--)cout<<"Case #"<<++_<<": "<<solve()<<'\n';
return 0;
}