题目:http://acm.hdu.edu.cn/showproblem.php?pid=3622
题意:有N对点,每对点中必须选择一个点,以每个选择点为中心画圆,每个圆的半径不作要求,但唯一限制的是所有圆不能相交,问画出来的所有圆中半径最小的值,最大是多少?
感觉跟《训练指南》中2-SAT这块的例题很类似。
对答案进行二分,把问题转化为特定半径的可行性判断。假设当前最小半径是mid,那么如果两个点(不是同一对的)的距离小于2*mid,那么说明两圆相交,有冲突,那么就连边到它们各自取反的点。然后就是很常规的2-SAT问题了。
PS:写的时候智商又捉急了。。。每次判断两个点是否冲突都去计算一次距离,TLE了很多下,纠结了很久才发现是这个地方大量的重复计算,而且还是很蛋疼地浮点数平方和开方,后来开了个数组记录这些结果,马上变成400+MS了,看来浮点数的威力还是不容小视。。。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;
#define pb push_back
#define N 200
vector<int> V[N];
const double eps = 1e-5;
double x[N], y[N], d[N][N];
int n;
double dist(int a, int b){
return sqrt(pow(x[a]-x[b],2.0) + pow(y[a]-y[b],2.0));
}
bool mark[N];
int S[N], c;
bool dfs(int x){
if(mark[x^1]) return 0;
if(mark[x]) return 1;
mark[x]=1;
S[c++]=x;
for(int i=0; i<V[x].size(); i++){
if(!dfs(V[x][i])) return 0;
}
return 1;
}
bool check(){
memset(mark,0,sizeof(mark));
for(int i=0; i<n*2; i+=2){
if(!mark[i] && !mark[i+1]){
c = 0;
if(!dfs(i)){
while(c>0) mark[S[--c]]=0;
if(!dfs(i+1)) return 0;
}
}
}
return 1;
}
void solve(){
double low=0.0, top=40000.0, mid;
while(top-low>eps){
mid = low+top;
for(int i=0; i<n*2; i++) V[i].clear();
for(int i=0; i<n*2; i++){
for(int j=i+1; j<n*2; j++){
if((i>>1)==(j>>1)) continue;
double tmp = d[i][j];
if(tmp<mid){
V[i^1].pb(j);
V[j^1].pb(i);
}
}
}
mid*=0.5;
if(check()) low=mid;
else top=mid;
}
printf("%.2lf\n", low);
}
int main(){
while(~scanf("%d", &n)){
for(int i=0; i<n; i++){
scanf("%lf %lf %lf %lf", x+i*2, y+i*2, x+i*2+1, y+i*2+1);
}
for(int i=0; i<n*2; i++){
for(int j=i+1; j<n*2; j++){
d[i][j] = d[j][i] = dist(i,j);
}
d[i][i]=0.0;
}
solve();
}
return 0;
}