题目概述
在一个无限大的平面里画n个圆。
每个圆有2个待选的放置位置。
限制条件为圆不相交,求最小半径的最大值。
解题思路
显然如果R满足限制条件,那么任意r(r
<
<script type="math/tex" id="MathJax-Element-1"><</script>R)肯定满足限制条件,想到用二分枚举答案。
接下来考虑如何验证。
不难发现这是一个2-sat问题。
假设位置
i,j
以当前最小半径做圆相交,显然可以得到
i−>j′
j−>i′
如果对这句话不理解 点这里
接下来用tarjan刷环,判断 i,i′ 是否在同一个环上就可了。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=205,maxm=40005;
int n,x[maxn],y[maxn],dfn[maxn],low[maxn],h[maxn];
int tot,lnk[maxn],son[maxm],nxt[maxm],top,s[maxn],time,G;
bool ins[maxn];
inline int _read(){
int num=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9') num=num*10+ch-48,ch=getchar();
return num*f;
}
int sqr(int x){return x*x;}
int getl(int i,int j){return sqr(x[i]-x[j])+sqr(y[i]-y[j]);}
void add(int x,int y){
nxt[++tot]=lnk[x];lnk[x]=tot;son[tot]=y;
}
void tarjan(int x){
dfn[x]=++time;low[x]=time;
s[++top]=x;ins[x]=1;
for (int j=lnk[x];j;j=nxt[j])
if (!dfn[son[j]]) {tarjan(son[j]);low[x]=min(low[x],low[son[j]]);}
else if (ins[son[j]]) low[x]=min(low[x],dfn[son[j]]);
if (dfn[x]==low[x]){
G++;
while (s[top]!=x){h[s[top]]=G;ins[s[top]]=0;top--;}
h[x]=G;ins[x]=0;top--;
}
}
bool check(double x){
memset(lnk,0,sizeof(lnk));tot=0;
for (int i=0;i<2*n;i++)
for (int j=0;j<i;j++)
if ((i/2!=j/2)&&getl(i,j)<(double)4*x*x) add(i,j^1),add(j,i^1);
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
time=0;G=0;
for (int i=0;i<2*n;i++) if (!dfn[i]) tarjan(i);
for (int i=0;i<n;i++) if (h[2*i]==h[2*i^1]) return 0;
return 1;
}
int main(){
freopen("exam.in","r",stdin);
freopen("exam.out","w",stdout);
while (scanf("%d",&n)==1){
for (int i=0;i<n;i++){
x[2*i]=_read();y[2*i]=_read();
x[2*i^1]=_read();y[2*i^1]=_read();
}
double L=0,ans,R=4e4,mid;
while (R-L>=1e-5){
mid=(L+R)/2;
if (check(mid)){ans=mid;L=mid;}else R=mid;
}
printf("%.2lf\n",ans);
}
return 0;
}