扫描线

算法模型分析:

·ACM中,扫描线被用来处理矩形相交后面积问题,十分易懂,代码也逻辑性明了,容易懂,类题代码类似,常与线段树搭配使用,时间复杂度低,但如果无法灵活处理线段树,那么学起来也比较艰辛!

·在比赛或者生活中,我们会经常遇到多个矩形叠放后,求并图形的面积,这是个非常明了的问题,一般最初级的思维:暴力求解---矩形切割

(参考: 薛矛 IOI2004国家集训队论文  《解决动态统计问题的两把利刃——剖析线段树与矩形切割)

·先挑二个矩阵切割放入队列中

·然后对后面所有的矩形依次与队列中的矩阵比较

  ·相交就切割后放入队列

  ·不相交直接放入队列

·比较完后,求队列中所有矩形的面积sum 即为面积并

时间复杂度:对于极端数据,为O(n^3),解题是会TLE的

 

·结合高等数学中积分求面积,将图形沿x轴,dx无限小,将图形切割成一个个连续的矩形,累加每个矩形,得到面积并,x是连续的。

  对于编程,我们很难处理这个连续的x,但在这里,我们的对象本身就是一个个现成的矩形,所以我们的x是离散的且仅有2n个;

  而我们仅需找到对应x的y值(为x=x0(代表每一矩形的竖边的x)与合并图形的相交的长度),所有竖边的x直线即为我们的扫描线;

    一个矩形有两条竖边(左x=x1、右x=x2),对于[x1,x2)之间的扫描线,该矩形与扫描线有交线,长度为矩形的竖边的长度;到了x2,该矩形的面积已经计算好了;

    即对应于两个过程,当扫描到x1,左竖边应加入线段树,更新出y的长度,进而求出x1与后继扫描线之间的面积;一直到x2,将其从线段树中删去;

 

 

线段树的模板:

·定义树节点:

 · 线段左右端点的序号l,r;(处理扫描线问题,我习惯将其视为闭合,且叶节点长度为1即r-l=1,而非点)

 ·len数组:用于记录每一段中,len[i]记录被覆盖了不小于i次的长度;

这就是要处理的区间问题;

 · cover记录整个区间长度被覆盖的次数

这是我们的懒标记,由于问到的是题目中整个区间的覆盖长度,也就是说仅查询节点1的覆盖长度,查询单一,即不用写query函数,而又因为一旦整个长度覆盖,即表示全部被覆盖,所以懒标记不用向下传递;

 

struct stnode_t{
    int l,r;
    double len;
    int cover;
}ST[Size<<2];

 

 

 

 

 

·建树:

   非常简单的递归mkSTree(int l,int r,int rt);

 ·赋值节点rt的l,r端点的序号为l,r;

 ·r-l==1,为叶节点,直接返回;

 ·得到左右子区间的分界点序号:mid=(l+r+1)>>1;

 ·创建左右子线段树:

       mkSTree(l,mid,lson);

       mkSTree(mid+1,r,rson);

 

void mkSTree(int l,int r,int rt){
    ST[rt].l=l;ST[rt].r=r;ST[rt].len=0;ST[rt].cover=0;
    if(r-l==1) return;
    int mid=(l+r+1)>>1;
    mkSTree(l,mid,lson);
    mkSTree(mid,r,rson);
}

 

 

 

·区间更新:

  1)为使代码更简洁:所以将增删函数合为更新函数!

  2)有两种设计模式:这里我们采取不更改目标区间的写法。

  ·如果目标区间包含了遍历的区间,那么该节点cover++或者cover--;

  ·否则 如果目标区间包含了该区间的左部,即更新左区间;

         如果目标区间包含了该区间的右部,即更新右区间;

  ·当前节点数据更新;

 

void update(double y1,double y2,int rt,int val){//val为1或-1
    if(y1<=Y[ST[rt].l]&&Y[ST[rt].r]<=y2)
        ST[rt].cover+=val;
    else{
        int mid=(ST[rt].r+ST[rt].l+1)>>1;
        if(y1<Y[mid])update(y1,y2,lson,val);
        if(y2>Y[mid])update(y1,y2,rson,val);
    }
    updata(rt);
}

 

 

 

 

 

·当前节点数据更新:

1)这是我们对不同的题目需要更改的地方,不同的题目对覆盖次数要求不同

   而写法不同,但格式一样!

以求多矩形面积并为例:

·如果当前节点cover>0,则len[1]=区间长度;

·否则(即未被整个区间覆盖)如果不是叶节点,len[1]=左右区间被覆盖的长度之和;

·否则为叶节点,len[1]为0;

 

void updata(int rt){
    if(ST[rt].cover>0)ST[rt].len=Y[ST[rt].r]-Y[ST[rt].l];
    else if(ST[rt].r-ST[rt].l==1) ST[rt].len=0;
         else ST[rt].len=ST[lson].len+ST[rson].len;
}

 

 

 

 

其实已经明白了扫描线问题就是线段树的一种特殊应用模板题!

 

实战应用:

 

·hdu 1542 Atlantis

题意:有n个矩形,给你左下点和右上点,问合并后的图形的面积;

·就是我们的例子,非常简单,只需将线段树中len类型换为double类型;

 

/*
*求矩形面积并
*线段树+扫描线


*/
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
#define lson (rt<<1)
#define rson ((rt<<1)|1)
#define Size 210
using namespace std;

struct stnode_t{
    int l,r;
    double len;
    int cover;
    int lb,rb;
}ST[Size<<2];

struct yline{
    double x;
    double y1,y2;
    int flag;
}edge[Size];

double Y[Size];
//建树
void mkSTree(int l,int r,int rt){
    ST[rt].l=l;ST[rt].r=r;ST[rt].len=0;ST[rt].cover=0;
    if(r-l==1) return ;
    int mid=(l+r+1)>>1;
    mkSTree(l,mid,lson);
    mkSTree(mid,r,rson);
}
//更新当前节点的数据
void updata(int rt){
    if(ST[rt].cover>0)ST[rt].len=Y[ST[rt].r]-Y[ST[rt].l];
    else if(ST[rt].r-ST[rt].l==1) ST[rt].len=0;
         else ST[rt].len=ST[lson].len+ST[rson].len;
}
//区间更新
void update(double y1,double y2,int rt,int val){
    if(y1<=Y[ST[rt].l]&&Y[ST[rt].r]<=y2) ST[rt].cover+=val;
    else{
        int mid=(ST[rt].l+ST[rt].r+1)>>1;
        if(y1<Y[mid]) update(y1,y2,lson,val);
        if(Y[mid]<y2) update(y1,y2,rson,val);
    }
    updata(rt);
}

bool cmp(yline &a,yline &b){
    if(a.x==b.x) return a.y1<b.y1;
    return a.x<b.x;
}
int main(){

    int n;int kase=0
  • 14
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值