zoukankan      html  css  js  c++  java
  • 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
    ", 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
    ",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;
    }
    一步一步,永不停息
  • 相关阅读:
    移动端网页使用flexible.js加入百度联盟广告样式不一致问题解决
    flexible.js移动端适配安卓高分辨不兼容问题
    vue select二级城市联动及第二级默认选中第一个option值
    设置滚动条样式
    windows mongodb最常用命令简单归纳
    vue用阿里云oss上传图片使用分片上传只能上传100kb以内的解决办法
    vue实现文章内容过长点击阅读全文功能
    vue获取dom元素注意问题
    input框取消光标颜色手机端不生效
    基于 Vue.js 的移动端组件库mint-ui实现无限滚动加载更多
  • 原文地址:https://www.cnblogs.com/Willems/p/12577902.html
Copyright © 2011-2022 走看看