浅谈扫描线算法的应用

浅谈扫描线算法的应用


关于扫描线

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

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

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

基本方法是将平行于坐标轴的矩形(博主能力有限只能处理到这种程度)分解为(看作)数条与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

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

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

 

原文地址:https://www.cnblogs.com/knife-rose/p/11216594.html