team2012-E1-mysol-0014
从 Trac 迁移的文章
这是从旧校内 Wiki 迁移的文章,可能存在一些样式问题,您可以向 memset0 反馈。
原文章内容如下:
{{{
/* 解题报告 //
N 组气球,每组两个,都固定在空间中给定的位置上
求最大的半径 R,使得每组气球中各选出一个,分别充气到半径恰好为 R
并且这些选出的气球两两之间都不会产生重叠
显然,在看到『每组两个,各选一个』,并根据半径 R 的不同
存在着一些单向的制约条件时,就会想到经典的 2-SAT 问题
做法便是二分枚举半径 R,如果在这个半径下
某两个气球 A 和 B 之间的距离小于 2 * R,则建立 A 到 B' 及 B 到 A' 的边
其中 A' 是 A 的同组异色的气球,B' 是 B 的同组异色的气球
这种建边方式意味着,由于 A 和 B 冲突了:
1. 如果选了气球 A,就一定要选气球 B'(因为 B 不能选)
2. 如果选了气球 B,就一定要选气球 A'(因为 A 不能选)
建立好这个有向图之后,判断是否存在任意的 A 和 A' 之间存在双向路径
即,如果选了 A 就一定要选 A',且如果选了 A' 就一定要选 A
想要判断是否出现这样的矛盾情况,方法是强联通缩点(因为速度比较快)
// By 猛犸也钻地 @ 2012.07.21 */
}}}
{{{
#include <cstdio>
#include <vector>
#include <cmath>
using namespace std;
class GabowSCC{
public:
static const int SIZE = 400;
int cnt,idx[SIZE];
int gao(int n, const vector<int> e[]){
Pc=Qc=-1;
for(int i=cnt=0;i<n;i++) idx[i]=tag[i]=-1;
for(int i=use=0;i<n;i++) if(tag[i]<0) dfs(i,e);
return cnt;
}
private:
int tag[SIZE],P[SIZE],Q[SIZE],use,Pc,Qc;
void dfs(int x, const vector<int> e[]){
tag[P[++Pc]=Q[++Qc]=x]=use++;
for(size_t i=0;i<e[x].size();i++){
int y=e[x][i];
if(~idx[y]) continue;
if(~tag[y]) while(tag[y]<tag[Q[Qc]]) Qc--;
else dfs(y,e);
}
if(Q[Qc]==x) Qc--; else return;
do idx[P[Pc]]=cnt; while(P[Pc--]!=x);
cnt++;
}
}scc;
vector<int> e[400];
double l[400][400];
int main(){
int n,x[400],y[400],z[400];
while(scanf("%d",&n)==1){
n*=2;
for(int i=0;i<n;i++){
scanf("%d%d%d",x+i,y+i,z+i);
for(int j=0;j<i;j++){
int dx=x[i]-x[j];
int dy=y[i]-y[j];
int dz=z[i]-z[j];
l[i][j]=sqrt(dx*dx+dy*dy+dz*dz);
}
}
double lo=0,hi=30000;
for(int c=1;c<=80;c++){
double r=(lo+hi)/2;
bool ok=true;
for(int i=0;i<n;i++) e[i].clear();
for(int i=0;i<n;i++) for(int j=0;j<i;j++){
if(l[i][j]>=2*r || i>>1==j>>1) continue;
e[i].push_back(j^1);
e[j].push_back(i^1);
}
scc.gao(n,e);
for(int i=0;i<n;i+=2) ok&=(scc.idx[i]!=scc.idx[i+1]);
if(!ok) hi=r; else lo=r;
}
int ans=int(lo*1000+1e-8);
printf("%d.%03d\n",ans/1000,ans%1000);
}
}
}}}
/* 解题报告 //
N 组气球,每组两个,都固定在空间中给定的位置上
求最大的半径 R,使得每组气球中各选出一个,分别充气到半径恰好为 R
并且这些选出的气球两两之间都不会产生重叠
显然,在看到『每组两个,各选一个』,并根据半径 R 的不同
存在着一些单向的制约条件时,就会想到经典的 2-SAT 问题
做法便是二分枚举半径 R,如果在这个半径下
某两个气球 A 和 B 之间的距离小于 2 * R,则建立 A 到 B' 及 B 到 A' 的边
其中 A' 是 A 的同组异色的气球,B' 是 B 的同组异色的气球
这种建边方式意味着,由于 A 和 B 冲突了:
1. 如果选了气球 A,就一定要选气球 B'(因为 B 不能选)
2. 如果选了气球 B,就一定要选气球 A'(因为 A 不能选)
建立好这个有向图之后,判断是否存在任意的 A 和 A' 之间存在双向路径
即,如果选了 A 就一定要选 A',且如果选了 A' 就一定要选 A
想要判断是否出现这样的矛盾情况,方法是强联通缩点(因为速度比较快)
// By 猛犸也钻地 @ 2012.07.21 */
#include <cstdio>
#include <vector>
#include <cmath>
using namespace std;
class GabowSCC{
public:
static const int SIZE = 400;
int cnt,idx[SIZE];
int gao(int n, const vector<int> e[]){
Pc=Qc=-1;
for(int i=cnt=0;i<n;i++) idx[i]=tag[i]=-1;
for(int i=use=0;i<n;i++) if(tag[i]<0) dfs(i,e);
return cnt;
}
private:
int tag[SIZE],P[SIZE],Q[SIZE],use,Pc,Qc;
void dfs(int x, const vector<int> e[]){
tag[P[++Pc]=Q[++Qc]=x]=use++;
for(size_t i=0;i<e[x].size();i++){
int y=e[x][i];
if(~idx[y]) continue;
if(~tag[y]) while(tag[y]<tag[Q[Qc]]) Qc--;
else dfs(y,e);
}
if(Q[Qc]==x) Qc--; else return;
do idx[P[Pc]]=cnt; while(P[Pc--]!=x);
cnt++;
}
}scc;
vector<int> e[400];
double l[400][400];
int main(){
int n,x[400],y[400],z[400];
while(scanf("%d",&n)==1){
n*=2;
for(int i=0;i<n;i++){
scanf("%d%d%d",x+i,y+i,z+i);
for(int j=0;j<i;j++){
int dx=x[i]-x[j];
int dy=y[i]-y[j];
int dz=z[i]-z[j];
l[i][j]=sqrt(dx*dx+dy*dy+dz*dz);
}
}
double lo=0,hi=30000;
for(int c=1;c<=80;c++){
double r=(lo+hi)/2;
bool ok=true;
for(int i=0;i<n;i++) e[i].clear();
for(int i=0;i<n;i++) for(int j=0;j<i;j++){
if(l[i][j]>=2*r || i>>1==j>>1) continue;
e[i].push_back(j^1);
e[j].push_back(i^1);
}
scc.gao(n,e);
for(int i=0;i<n;i+=2) ok&=(scc.idx[i]!=scc.idx[i+1]);
if(!ok) hi=r; else lo=r;
}
int ans=int(lo*1000+1e-8);
printf("%d.%03d\n",ans/1000,ans%1000);
}
}