zoukankan      html  css  js  c++  java
  • 浅谈扫描线算法的应用

    浅谈扫描线算法的应用


    关于扫描线

    扫描线严格来说是一种思想(说了等于没说系列

    本蒟蒻在看其他人博客的时候学的一脸蒙蔽,在刷了几道题目之后才略有感触

    扫描线可以理解为在处理二维平面时将平面分割为数条平行线段,再通过数据结构动态维护各线段求解一类平面问题(包括不限于矩形面积的交并)

    基本方法是将平行于坐标轴的矩形(博主能力有限只能处理到这种程度)分解为(看作)数条与y轴平行的线段,再以x轴为基准将扫描线缓慢向右推进,每推进一步更新线段左右端点。

    通常结合离散化食用

    直接上题目吧QwQ,结合题目分析扫描线应用


    各位期待已久(并没有)的例题

    洛谷P2061

    题目概述:有一些矩形,底在x轴上,给出左右端点和高度,彼此之间可能有重叠部分,求轮廓覆盖的总面积(求矩形面积并)

    分析:

      首先离散化大家都懂,将x轴和y轴全部离散,再开一个数组记录离散后大小对应的真实大小,这里还是贴一下离散的代码防止后面有不理解的变量

      

        n=read();
        for(int x,y,h,i=1;i<=n;++i)
        {
            x=read(),y=read();h=read();
            e[++tot].x=x;
            e[tot].h=h;
            e[tot].id=i;
            e[++tot].x=y;
            e[tot].h=h;
            e[tot].id=i;
        }
        e[0].x=e[0].h=-1e9+7;//防止有 0 存在 
        sort(e+1,e+tot+1,cmq);//x轴离散 
        for(int i=1;i<=tot;++i)
        {
            ++totx;
            dis[totx]=e[i].x;
            int now=e[i].id;
            a[now].x==0?a[now].x=totx:a[now].y=totx;
        }
        sort(e+1,e+tot+1,cmp);//y轴离散 
        for(int i=1;i<=tot;++i)
        {
            if(e[i].h^e[i-1].h)
            {
                ++toty;
                up[toty]=e[i].h;
            }
            int now=e[i].id;
            a[now].h=toty;
        }
        e[0].h=0;

        下一步,我们需要将每条线段插入合适的地方,这里可以用到差分的思想,我们将每条线段对应的左端点在对应x轴位置上打上一个标记,扫描线遇到这个标记就将其插入数据结构中,在右端点再打上另一个标记,扫描线遇到就将线段删除,这里可以用vector数组实现

    for(int i=1;i<=n;++i)
        {
            int l=a[i].x;
            int r=a[i].y;
            ad[l].push_back(i);//插入 标记 
            cd[r].push_back(i);//删除 标记 
        }

      接下来就是扫描线推进的过程了,其实只要一个循环模拟就够了,每次利用线段树维护线段左右端点,由于本题目下端点一律是x轴,所以只要维护上端点就够了

        for(int i=1;i<=totx;++i)
        {
            int sum=cd[i].size();
            for(int k=0;k<sum;++k)// 检查 删除 标记 
            {
                int now=cd[i][k];
                update(1,toty,1,a[now].h,-1);//-1 删除 线段 
            }
            sum=ad[i].size();
            for(int k=0;k<sum;++k)// 检查 插入 标记 
            {
                int now=ad[i][k];
                update(1,toty,1,a[now].h,1); // 1 插入 线段 
            }
            int len=query(1,toty,1);
            if(dis[i+1]-dis[i]>=0)
                res+=(dis[i+1]-dis[i])*up[len];// 计算 答案 
        }
        printf("%lld
    ",res);

      关于线段树的维护方法:

        我们以y轴坐标为叶子结点,建立一颗权值线段树,每次更新将对应节点数组+1,在区间中,>0表示该节点当前被线段覆盖,==0表示该节点目前没有线段

        再根据线段树二分的特性,每次我们只要找到最右边有数字的叶子节点,就可以知道矩形上端点在哪里了

        更新操作与一般线段树相同就不放了,这里放下查询

        

    inline int query(int l,int r,int p)
    {
        if(l==r) return ans[p]?l:0;//注意 特判 0 !!! 
        int mid=(l+r)>>1;
        if(ans[rs(p)])
            return query(mid+1,r,rs(p));
        else 
            return query(l,mid,ls(p));
    }

      这样矩形面积的交基本结束QwQ 求并的方法类似,只要记录区间线段覆盖最大值就可以找到左右端点QwQ

    有什么意见或者觉得博客有什么不足的地方欢迎各位留言,如果收到留言我会很高兴的(假装这里有一个高兴的表情)

    博客根据作者能力不定期更新,也许下次再见就更丰富了呢

     

  • 相关阅读:
    JS-两个空数组为什么不相等?
    ES6---箭头函数()=>{} 与function的区别(转载)
    SASS用法指南
    scss/less语法以及在vue项目中的使用(转载)
    基于vue+mint-ui的mobile-h5的项目说明
    vue中mint-ui的filed的与blur事件结合实现检查用户输入是否正确
    Carrierwave 如何配置合理的上传文件名(转自李华顺)
    ruby大神与菜鸟的代码区别
    用imageMagick合成图片添加图片水印
    想做喜欢的安卓应用
  • 原文地址:https://www.cnblogs.com/knife-rose/p/11216594.html
Copyright © 2011-2022 走看看