题目大意:
在一个平面上,放炸弹。每次给出两个位置,在这两个位置里面选择一个放炸弹。一共给出n组位置,如前面所述,每组有个两个位置。然后,对于每个炸弹,都有一个power,即爆炸时的破坏的半径,这个半径是可以控制的,几就是说,以所选的位置为圆心的半径可以控制的圆。要求圆和圆之间不能有交集,可以相切。求解,满足n个圆无交集的最大的半径是多少?
题目分析:
首先,是这个半径应该是多少,其实要求n个圆的半径里面最小的是多少,我们就可以假设这n个圆的半径是相同的,因为相同的一组数中,最小值就是这个数。那么半径是多少呢,我们可以用二分查找来找,每确定一个半径,就判断一下这个半径是否满足要求,如果满足,就再试更大的半径,否则将半径值减少,用二分来实现
接下来,这道题,是要从每对点选一个,也就是说二选一,那就像真和假选一个,并且要求半径满足条件,很显然是2-SAT的模型,这里是用2-SAT去判定半径值是否成立,以及半径要增大还是减小还是正好是最大。
下面是本题的代码,里面有很多借鉴的东西,代码中间也有注释
#include <cstdio>
#include <cstring>
#include <cmath>
#define esp 1e-5
const int N = 210;
const int M = 4*101*101;
struct Node { //这个是用来记录点的坐标的
double x, y;
}s[N];
struct edge { //这是用来存储边信息的
int next, to;
}e1[M], e2[M]; //e1是用来存储队列1的边的信息,e2是队列2的边信息,这用来构造两个图的
int n, head1[N], head2[N], id1, id2;
bool vis1[N], vis2[N];
int T[N], Belong[N], Tcnt, Bcnt;
void init() {
memset( head1, -1, sizeof(head1) );
memset( head2, -1, sizeof(head2) );
memset( vis1, 0, sizeof(vis1) );
memset( vis2, 0, sizeof(vis2) );
id1 = id2 = 0;
Bcnt = Tcnt = 0;
}
void add( int i, int j ) {
e1[id1].to = j, e1[id1].next = head1[i], head1[i] = id1++; //本次尝试选定的边加到e1中
e2[id2].to = i, e2[id2].next = head2[j], head2[j] = id2++; //本次尝试未选定的边加到e2中,e2 是 e1 的逆图
}
void dfs1( int x ) {
vis1[x] = true;
for ( int j = head1[x]; j != -1; j = e1[j].next ) if ( !vis1[e1[j].to] ) dfs1( e1[j].to );
T[Tcnt++] = x;
}
void dfs2( int x ) {
vis2[x] = true;
Belong[x] = Bcnt;
for ( int j = head2[x]; j != -1; j = e2[j].next ) if ( !vis2[e2[j].to] ) dfs2( e2[j].to ); //因为e2是e1的逆向,所以这里是在找每个点在原图中的前驱
}
bool ok( int tol ) {
for ( int i = 0; i < 2*tol; ++i ) if ( !vis1[i] ) dfs1( i );
for ( int i = Tcnt-1; i >= 0; i-- )
if ( !vis2[T[i]] ) {
dfs2(T[i]);
Bcnt++;
}
for ( int i = 0; i <= n*2-2; i += 2 )
if ( Belong[i] == Belong[i+1] ) return false; //如果一对点在一个连通分量里面,说明冲突
return true;
}
double dist( Node a, Node b ) {
return sqrt( (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) );
}
int main()
{
while ( scanf("%d", &n) != EOF ) {
for ( int i = 0; i < n; ++i ) scanf("%lf%lf%lf%lf", &s[i*2].x, &s[i*2].y, &s[i*2+1].x, &s[i*2+1].y);
double left, right, radius;
left = 0.0, right = 40000.0;
while ( right - left >= esp ) { //二分查找确定半径
radius = ( left + right ) / 2;
init();
for ( int i = 0; i < 2*n-2; ++i ) {
int t;
if ( i%2 == 0 ) t = i+2; //这是在找i的下一组位置,i和它的同伴是不能连起来的
else t = i + 1;
for ( int j = t; j < n*2; ++j )
if ( dist(s[i], s[j]) < radius*2 ) { // 说明有冲突,即这两个点距离不满当前的半径,所以他两只能选一个
add( i, j^1 );
add( j, i^1 );
}
}
if( ok(n) ) left = radius; //判断是否满足要求,如果满足,就让半径增大,不满足就说明当前半径大了,应当减小
else right = radius;
}
printf("%.2lf\n", radius);
}
}