HDU 3124 Moonmist (扫描线 + 二分 + 平面最近圆对的距离)

题目:传送门

题意

给你 n 个互不相交的圆的圆心和半径,问你最近的两个圆的距离是多少,T个测试。

2 <= N <= 50000  1 <= T <= 10

思路

参考博客

二分每个圆半径的增量,如果每个圆的半径增加了 mid 之后存在圆的相交,那么这个 mid 就偏大了,如果不相交,那么就是 mid 偏小了,那么就可以这样二分了。

现在,考虑怎么比较快的判断圆是否相交,这里考虑使用扫描线。

以下转载自:http://blog.sina.com.cn/s/blog_6e7b12310100qnex.html

 

 

第一条扫描线从左往右依次是每个圆的左边界,即竖直线L1,L2,L3。

        第二条扫描线从左往右依次是每个圆的右边界,即竖直线R1,R2,R3。

        两条扫描线均是从最左边的L1和R1开始,保证L扫描线的x坐标永远小于R扫描线的x坐标即可。扫描流程如下,假设有n个圆,i为Li,j为Rj:

        i = j = 1

        while(i <= n or j <= n)

        {

                  if(i == n + 1) 删除圆j,j++;

                  else if (j == n + 1) 插入圆i,检测圆i的圆心和y方向相邻两圆的碰撞情况。i++。

                  else if (i <= n and Li 在Ri的左边)

                           插入圆i,检测圆i的圆心和y方向相邻两圆的碰撞情况。i++。

                  else 删除圆j,j++;

        }

简要解释如下:

While中有4个分支

前两个是边界情况,很容易理解。

第三个是L扫描线的推进,即插入圆。

第四个条件:由于只需检测Li 和 Rj两条扫描线之间的圆的相交情况,所以,Li之前的圆都需要删除

当扫描线为Li和Rj时,已经插入的圆都是x方向起点小于等于Li,x方向终点大于等于Rj,即在Li和Rj之间是连续不间断的,所以只需检测插入圆的圆 心的上下相邻的两个即可,不可能跳跃。即假设圆心位置从下到上一次编号1~m,那么插入圆x后,不可能出现x和x+2相交而不和x+1相交的情况(应为在 Li和Rj之间是连续的)。

至于检测的方法就是线段树或者set了。

上图的扫描线过程是:

初始为L1,R1

L1 < R1, 插入圆A,检测无碰撞,变为L2,R1

L2 < R1, 插入圆B,检测无碰撞,变为L3, R1

L3 > R1, 删除圆B,变为L3,R2

L3 < R2,插入圆C,检测到碰撞,结束

 

 

#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define mem(i, j) memset(i, j, sizeof(i))
#define rep(i, j, k) for(int i = j; i <= k; i++)
#define dep(i, j, k) for(int i = k; i >= j; i--)
#define pb push_back
#define make make_pair
#define INF INT_MAX
#define inf LLONG_MAX
#define PI acos(-1)
#define fir first
#define sec second
using namespace std;

const int N = 1e6 + 5;
const double eps = 1e-10;

struct Point {
    double x, y;
    Point(double x = 0, double y = 0) : x(x), y(y) { }
};

Point operator + (Point A, Point B) { return Point(A.x + B.x, A.y + B.y); }
Point operator - (Point A, Point B) { return Point(A.x - B.x, A.y - B.y); }
Point operator * (Point A, double p) { return Point(A.x * p, A.y * p); }
Point operator / (Point A, double p) { return Point(A.x / p, A.y / p); }
double Cross(Point A, Point B) { return A.x * B.y - A.y * B.x; }
double Dot(Point A, Point B) { return A.x * B.x + A.y * B.y; }
double Length(Point A) { return sqrt(Dot(A, A)); }

int dcmp(double x) {
    if(fabs(x) < eps) return 0; return x < 0 ? -1 : 1;
}

bool operator < (Point A, Point B) {
    return A.x < B.x || (A.x == B.x && A.y < B.y);
}

double x[N], y[N], r[N], mid;
int L[N], R[N], ran[N], ran_pos[N], n;

bool cmpL(int a, int b) {
    return x[a] - r[a] < x[b] - r[b];
}
bool cmpR(int a, int b) {
    return x[a] + r[a] < x[b] + r[b];
}
bool cmpran(int a, int b) {
    return y[a] < y[b] || (y[a] == y[b] && x[a] < x[b]);
}

set < int > Q;

bool ok(int a, int b) {
    a = ran[a];
    b = ran[b];
    double tmp1 = Dot(Point(x[a], y[a]) - Point(x[b], y[b]), Point(x[a], y[a]) - Point(x[b], y[b]));
    double tmp2 = (r[a] + r[b] + mid + mid) * (r[a] + r[b] + mid + mid);
    if(tmp1 <= tmp2) return true;
    else return false;
}

bool add(int a) {
    set < int > :: iterator it;
    it = Q.insert(a).first; /// first指向当前插入的点的位置
    if(it != Q.begin()) {
        if(ok(a, *--it)) return true;
        it++;
    }
    if(++it != Q.end()) {
        if(ok(a, *it)) return true;
    }
    return false;
}

bool judge() {
    Q.clear();
    int i = 1, j = 1;
    while(i <= n || j <= n) {
        if(j == n + 1 || (i != n + 1 && x[L[i]] - r[L[i]] - mid < x[R[j]] + r[R[j]] + mid)) {
            if(add(ran_pos[L[i++]])) return true;
        }
        else Q.erase(ran_pos[R[j++]]);
    }
    return false;
}

void get_ans() {
    double nowl = 0, nowr = Dot(Point(x[1], y[1]) - Point(x[2], y[2]), Point(x[1], y[1]) - Point(x[2], y[2])) - r[1] - r[2];
    while(nowr - nowl > eps) {
        mid = (nowl + nowr) * 0.5;
        if(judge()) nowr = mid;
        else nowl = mid;
    }

    printf("%.6f\n", nowl + nowr);
}

void solve() {
    scanf("%d", &n);

    rep(i, 1, n) {
        scanf("%lf %lf %lf", &x[i], &y[i], &r[i]);
        L[i] = R[i] = ran[i] = i;
    }
    sort(L + 1, L + 1 + n, cmpL);
    sort(R + 1, R + 1 + n, cmpR);
    sort(ran + 1, ran + 1 + n, cmpran);
    rep(i, 1, n) ran_pos[ran[i]] = i;

    get_ans();

}

int main() {
    int _; scanf("%d", &_);
    while(_--) solve();

    return 0;
}

 

 

有注释的代码:出处

 

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <set>
#include <algorithm>
using namespace std;
#define left Left
#define right Right
#define eps 1e-8
#define maxn 60000
double x[maxn],y[maxn],r[maxn];
int left[maxn],right[maxn],up[maxn],rank_up[maxn];
int n;
set<int> my_set;
double mid;
bool cmp_left(const int &a,const int &b)
{
    return x[a]-r[a] < x[b]-r[b];
}
bool cmp_right(const int &a,const int &b)
{
    return x[a]+r[a] < x[b]+r[b];
}
bool cmp_up(const int &a,const int &b)
{
    if(y[a]==y[b])
    return  x[a] < x[b];
    else return y[a] < y[b];
}
double my_sqr(double a)
{
    return a*a;
}
bool is_cross(int a,int b)//檢測高度排名為a和b的圓是否相交
{
    a=up[a],b=up[b];
    double t1,t2;
    t1=my_sqr(x[a]-x[b])+my_sqr(y[a]-y[b]),t2=my_sqr(r[a]+r[b]+mid+mid);
    if(t1<=t2)
    return true;//表示相交
    return false;
}
/*
 * 這個函數插入的是當前在集合裡面圓心y值,每次再插入
 * 的同時返回插入的位置,由於set是排序了的,所以前後相鄰位置
 * 的大小關係也是相鄰的,那麼在檢測當前插入的這個圓是否與當前集合裡面的圓相交
 * 就只要判斷當前插入的圓與其上下兩個圓是否相交就OK了
 */
bool insert(int a)//這個函數在插入的同時檢測是否發生相交的情況
{
    set<int>::iterator it=my_set.insert(a).first;//這個first指向當前插入的這個元素位置的迭代器
    if(it!=my_set.begin())
    {
        if(is_cross(a,*--it))//相交
        return true;
        it++;//恢復
    }
    //注意set開始位置是元素,結束位置是位置,所以處理起來就不同了
    if(++it!=my_set.end())
    {
        if(is_cross(a,*it))
        return true;
    }
    return false;
}
bool is_ok()
{
    my_set.clear();
    int i=0,j=0;
    while(i<n || j<n)
    {
        if(j==n ||(i!=n && x[left[i]]-r[left[i]]-mid<x[right[j]]+r[right[j]]+mid))
        {
            /*這裡代碼在處理的時候插入的是y坐標的排名,這樣的好處是插入set是
             *排序的,這樣就能保證set某個元素的前後位置就是在真實圓的上下相鄰位置,所以這裡每次插入的是排名
             *也就是插入的不是元素,是排名,在插的時候直接插你排第幾
             */
            if(insert(rank_up[left[i++]]))
                return true;//
        }
        else
        my_set.erase(rank_up[right[j++]]);
    }
    return false;
}
double find_ans()
{
    double l=0,ri=sqrt(my_sqr(x[0]-x[1])+my_sqr(y[0]-y[1]))-r[0]-r[1];///求最小的距離嘛,左值為0,右邊隨便取一個兩個圓距離就OK了
    while(l+eps<ri)///這裡不加eps可能對於某些測試數據導致超時
    {
        mid=(l+ri)/2;
        if(is_ok())
        ri=mid;
        else
        l=mid;
    }
    printf("%.6lf\n",l+ri);
    return 0;
}
int main()
{
    int t,i,j,k;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(i=0;i<n;i++)
        scanf("%lf%lf%lf",&x[i],&y[i],&r[i]);
        for(i=0;i<n;i++)
        {
            left[i]=i;
            right[i]=i;
            up[i]=i;
        }
        sort(left,left+n,cmp_left);
        sort(right,right+n,cmp_right);
        sort(up,up+n,cmp_up);
        for(i=0;i<n;i++)
        rank_up[up[i]]=i;
        find_ans();
    }
    return 0;
}

 

posted on 2020-03-26 22:20  Willems  阅读(174)  评论(0编辑  收藏  举报

导航