HDU 3622 Bomb Game( 二分查找+2-SAT )

题目大意:

在一个平面上,放炸弹。每次给出两个位置,在这两个位置里面选择一个放炸弹。一共给出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);
    }
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值