扫描线

算法模型分析:

·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(y1Y[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 
#include 
#include 
#include 
#include 
#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
 

 

·hdu 1255 覆盖的面积

  题意:有n个矩形,给你左下点和右上点,求至少被覆盖了两次的区域面积。

·每次扫描的长度变为扫描线与至少两个矩形相交的线段,则我们需要用len[2]记录当前区间至少覆盖了两次的长度,len[1]记录当前区间至少覆盖了一次的

长度,而处理的区间问题是len[2],len[1]仅为辅助状态,参与其中!样例:第一组给的输出有问题,正确应该为7.62,不要被忽悠了!

 

/*
*矩形面积交
*/
#include 
#include 
#include 
#include 
#include 
#define lson (rt<<1)
#define rson ((rt<<1)|1)
#define Size 2010
using namespace std;

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

struct yline{
    double x,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].cover=0;
    memset(ST[rt].len,0,sizeof(ST[rt].len));
    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>=2)
        ST[rt].len[1]=ST[rt].len[2]=Y[ST[rt].r]-Y[ST[rt].l];
    else if(ST[rt].cover==1){
        ST[rt].len[1]=Y[ST[rt].r]-Y[ST[rt].l];
        if(ST[rt].r-ST[rt].l==1)ST[rt].len[2]=0;
        else ST[rt].len[2]=ST[lson].len[1]+ST[rson].len[1];
    }else{
         if(ST[rt].r-ST[rt].l==1)
             ST[rt].len[1]=ST[rt].len[2]=0;
         else{
            ST[rt].len[1]=ST[lson].len[1]+ST[rson].len[1];
            ST[rt].len[2]=ST[lson].len[2]+ST[rson].len[2];
         }
    }
}
//区间更新
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].r+ST[rt].l+1)>>1;
        if(y1Y[mid])update(y1,y2,rson,val);
    }
    updata(rt);
}

bool cmp(yline &a,yline &b){
    if(a.x==b.x) return a.y1

 

hdu 1828  Picture

 

题意:墙上挂了n张照片,给你左下点和右上点,矩形照片都水平放置,问组成的图形的周长;

·用扫描线分析,可得求周长时,求水平边的周长即寻找扫描线与并图形的交点个数(相交非连续线段的个数的二倍);

·求竖直边,通过两条相邻的扫描线len的绝对值差;因为len之差表示有线段增加或删除,成为并图形的外边部分;

·在线段树中,我们需要增加1个区间讨论的问题区间覆盖的不连续的线段的条数;

  这个问题:在子区间和父亲区间的联系上,需要增加区间的左端lbt、右端rbt是否覆盖,

(覆盖值为1),父亲节点的不连续的线段数就为:segnum=左区间的segnum+右区间的segnum-lbt*rbt,lbt与rbt同为1,2条合并1条;

·编程时,注意:题目输入有一句话:“Please process to the end of file”,

  请处理到结尾,这个在数据结构这个章节,我已经把题目当成一次case有3次了,请注意!

·还有更新时,去判断区间的适合Y[mid],Y[l],Y[r],不是mid、l、r,它们只是数组标号;

 

 

 

 

/*
* 周长交
* 线段树+扫描线

*/
#include 
#include 
#include 
#include 
#include 
#define lson (rt<<1)
#define rson ((rt<<1)|1)
#define Size 10010
using namespace std;

struct stnode_t{
    int l,r;
    int cover;
    int len;
    int lbt,rbt;
    int segnum;
}ST[Size<<2];
struct yline{
    int x,y1,y2;
    int flag;

}edge[Size];
int Y[Size];
//建树
void mkSTree(int l,int r,int rt){
    ST[rt].l=l;ST[rt].r=r;ST[rt].cover=ST[rt].len=ST[rt].lbt=ST[rt].rbt=0;
    ST[rt].segnum=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];
        ST[rt].lbt=ST[rt].rbt=1;
        ST[rt].segnum=1;
    }else if(ST[rt].r-ST[rt].l==1){
        ST[rt].len=0;
        ST[rt].lbt=ST[rt].rbt=0;
        ST[rt].segnum=0;
    }else{
        ST[rt].len=ST[lson].len+ST[rson].len;
        ST[rt].lbt=ST[lson].lbt;ST[rt].rbt=ST[rson].rbt;
        ST[rt].segnum=ST[lson].segnum+ST[rson].segnum-ST[lson].rbt*ST[rson].lbt;
    }
}
//区间更新
void update(int y1,int y2,int rt,int val){
    if(y1<=Y[ST[rt].l]&&Y[ST[rt].r]<=y2)//祖宗,害苦我了,改了mid,没有改你
        ST[rt].cover+=val;
    else{
        int mid=(ST[rt].l+ST[rt].r+1)>>1;
        if(y1Y[mid]) update(y1,y2,rson,val);
    }
    updata(rt);
}

bool cmp(yline &a,yline &b){
     if(a.x==b.x)return a.y1

 

 

 

 

 

hdu 4419 Colourful Rectangle

 扫描线_第1张图片

题意:给n个颜色的矩形(仅有R、G、B),给出左下点和右上点,问7种颜色的区域面积;

(R,G,B,RG,RB,GB,RGB)

·状态压缩:R、G、B代表3位2进制的第1、2、3位,有就为1,无则为0;

R:1;G:2;B:4;RG:3;RB:5;GB:6;RGB:7;

·所以len[i]不是表示覆盖了i次的长度,而表示该区间i颜色的长度;

·同时需要整个区间覆盖也需要记录清楚,被什么颜色整体覆盖;

· cover[i]记录被G[i]颜色覆盖的次数(G[1]=4,G[2]=2,G[3]分别表示B、G、R色)

·1)如果一段区间被3种颜色都覆盖了,那它的len[7]=区间长度;其余颜色长度为0;

 2)如果一段区间被2种颜色都覆盖了(假设为R、G),那区间仅有两种颜色的片段(RG和RGB);len[7]=左右子区间的B、RB、GB、RGB的片段长度;RG:len=区间长度-len[7];其余为0

 3) 如果一段区间被1种颜色覆盖了(假设为R),那么区间只有R、RG、RB、RGB四种颜色的片段(这个用&判断);(除了R,它们的长度是左右儿子中本身颜色和去掉R后的颜色长度之和,R是区间长度减去其它三种颜色长度)

 4)如果没有被覆盖,分叶节点和非叶节点,叶节点全是0;非叶节点各长度是左右子区间对应颜色的长度之和;

·时间上只增加了8倍,常数倍;

·编程上:一直wa;原因是我先将全部置0,然后将区间覆盖的颜色长度标为区间长度,可是非叶节点的讨论时,我省去了区间覆盖颜色t==0的判断,造成t==3也变成了t==0的情况;

/*
*面积并 + 扫描线
*每段线段有7种可能 用到状态压缩(方便状态之间转移)

*/
#include 
#include 
#include 
#include 
#include 
#define Size 50010
#define lson (rt<<1)
#define rson ((rt<<1)|1)
#define llt long long
using namespace std;
llt Y[Size<<1];
struct stnode_t{
    int l,r;
    int cover[4];
    llt len[8];
}ST[Size<<3];
int g[4]={0,4,2,1};
struct edge{
    int x,y1,y2;
    int flag;
    int color;
    bool operator <(edge &a){
        return x>1;
    mkSTree(l,mid,lson);
    mkSTree(mid,r,rson);
}
//当前节点数据更新
void updata(int rt){
    int a[3];int t=0;
    memset(a,0,sizeof(a));
    for(int i=1;i<4;++i)
        if(ST[rt].cover[i])a[t++]=g[i];
    int b=a[0]+a[1]+a[2];
    for(int i=1;i<8;++i) ST[rt].len[i]=0;
    if(b)ST[rt].len[b]=Y[ST[rt].r]-Y[ST[rt].l];

    if(ST[rt].r-ST[rt].l!=1){
        if(t==2){
            ST[rt].len[7]=ST[lson].len[7]+ST[lson].len[7-a[0]]+ST[lson].len[7-a[1]]+ST[lson].len[7-a[0]-a[1]]
                         +ST[rson].len[7]+ST[rson].len[7-a[0]]+ST[rson].len[7-a[1]]+ST[rson].len[7-a[0]-a[1]];
            ST[rt].len[b]-=ST[rt].len[7];
        }else if(t==1){
            for(int i=b+1;i<8;++i){
            if(i&b){
                ST[rt].len[i]=ST[lson].len[i]+ST[lson].len[i-b]
                             +ST[rson].len[i]+ST[rson].len[i-b];
                ST[rt].len[b]-=ST[rt].len[i];
            }

        }else if(t==0){//这里不能将t==0判断条件去掉,t可能为3、4;
            for(int i=1;i<8;++i)
               ST[rt].len[i]=ST[lson].len[i]+ST[rson].len[i];
        }
        }
}
/*
曾经找数据想找出这个bug:需要判断t==0
4
G 0 0 100 100
R 1 1 100 100
B 0 0 1 1
B 3 3 100 100
但得到三色区域为正确的
因为数据中三色边恰好为叶节点。
太可惜了!
*/
//区间更新
void update(int y1,int y2,int rt,int color,int val){
    if(y1<=Y[ST[rt].l]&&Y[ST[rt].r]<=y2)
        ST[rt].cover[color]+=val;
    else{
        int mid=(ST[rt].l+ST[rt].r+1)>>1;
        if(y1Y[mid]) update(y1,y2,rson,color,val);
    }
    updata(rt);
}
llt len[8];
int main(){

    int T;scanf("%d",&T);int kase=0;
    while(T--){
        int n;scanf("%d",&n);
        char c[2];int x1,y1,x2,y2;
        for(int i=0;i

 

hdu 3642 Get The Treasury

 

题意:有n个长方体(最多1000个),给出最小、最大x、y、z坐标,求相交3次及以上的体积值。

·进阶题,周长时点积分成线段;面积时,是线段积分成面积;现在,是面积积分成体积;

·开始,求覆盖面积,先离散y、z,把区域化为一个个矩形区域,区间问题:覆盖了超过2次的面积,二维面积树结果MLE了;

·上面的思考完全是想仿照进阶思想,我们可以用一维线段树来将每层z中所有矩形求覆盖了超过2次的面积;只是我们要人工选拔每层矩阵,时间复杂度最多O(n^2);

·我们需要设计矩形类型来完成排序操作;

·首先,需要对全部长方体的上下底面排序;(后面会讲不需记录上底面)

1)按z的大小排序,由小到大;

·按照之前,还是上底面flag=-1,出现即删除下底面;下底面flag=1,出现即加入该层求面积;可是在设计Slevel函数处理当前z层的超过2层的覆盖的面积时,我们会处理哪些rectangle会在下一层删除,连通建扫描边是一并处理;所以我们仅需设置下底面删除的层的z值为flag(删除标记);

long long Slevel(int t,int m){
    long long S=0; g=0;
    for(int i=0;i

·我们最多进行500层求覆盖2次以上的面积的操作,时间复杂度最坏为O(n^2logn),这里可进行500次最坏的情形,完全不用担心!

/*
非二维线段树+扫描线
二维超内存
一维求每层面积 时间:1154ms
*/
#include 
#include 
#include 
#include 
#include 
#include 
#define Size 1010
#define lson (rt<<1)
#define rson ((rt<<1)|1)

using namespace std;
int Z[Size*2],Y[Size*2];
struct stnode_t{
    int l,r;
    int cover;
    int len[4];
}ST[Size<<3];

struct rectangle{
    int z;
    int x1,y1,x2,y2;
    int flag;//死亡标记;
    bool operator <(rectangle &a){
        return z>1;
    mkSTree(l,mid,lson);
    mkSTree(mid,r,rson);
}

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

void update(int y1,int 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(y1Z[i]) break;
                F[g++]=d;
                ++d;
            }
            //cout<<(rect[i+1].z-rect[i].z)<

 

Poj 2482 Stars in Your Window

 

*这题很新颖,是道与现实规划很相关的问题!

题意: 给你N颗星星的二维坐标及亮度,求最优放宽度为M,高度为H的窗户亮度最大值。(星星不能在边上);

·M--,H--,即问题变成了星星可以在边上了;

·一颗星星(x0,y0)能够出现在中心(x,y)且(x0-M/2)<=x<=(x0+M/2)且(y0-H/2)<=x<=(y0+H/2)的窗户中;

·问题转变为了: 给你N个长宽相同的矩形的中心,重叠区域亮度累加,重叠区域代表能做窗户中心且将那几个矩形的中心在这个窗户上的点区域;(找亮度最大区域的点为中心放窗户,问亮度最大);

·我稍微将这个变动了一下:

一颗星星代表矩形的下角,扫描到矩形的左竖边,在线段树中这段区域全加星星的亮度,实际上扫描到了窗户以该星星的x为最右方时,所有线段树中的叶节点的y值,代表此时窗户的最上方;在左竖边与右竖边的之间扫描,这颗星星都能在以该扫描线为最右边,以竖边上的y为最上方的这个窗户上,所以直到右竖边来到,星星就照到后面的窗户了;(不是已窗户的中心来分析,而是分析窗户的右上点,星星能照到的所有窗户的右上点组成的区域矩形)

·原来线段树讨论的是区间的长度,现在分析区间上的最大值;

·完成这个想法,必须要对star对应的矩形排序(这里把yline改名为Point,因为矩形H相同,所以只记录下边y)

1) 先按x排序,从左到右

2) X相同,则右竖边小于左竖边,判flag大小(这是因为防止于此同时,本该可以照到这个左竖边上点为右上点的,却因为右竖边先出现在前,而删除导致答案出错!)

3) 其余随意;

 ·编程中:注意因为没有将另外一条上方的y记录下,导致线段树修改时出问题,举个例

           子:如果线段树将2、6、10、18做成线段树,修改区间为[17,17],因为17永远

           小于18,一直到18这个叶节点,进入18的左儿子0,0的儿子还是0,Y[0]=2,

           死在里面了!

           Running Error!还有线段树节点应该是原星星数n的8倍;4倍就TML了!

 

/*
*线段树+扫描线
*区间修改+标记
*/
#include 
#include 
#include 
#include 
#include 
#define Size 10010
#define lson (rt<<1)
#define rson ((rt<<1)|1)
#define llt long long
using namespace std;

llt Y[Size<<1];
struct stnode_t{
    llt l,r;
    llt flag;
    llt maxn;
}ST[Size<<3];

struct Point{
    llt x,y;
    llt flag;
    llt bright;
}star[Size<<1];

bool cmp(const Point &b,const Point &a){
    if(b.x==a.x){
            if(b.flag==a.flag)return b.ya.flag;
        }
        return b.x>1;
    mkSTree(l,mid,lson);
    mkSTree(mid+1,r,rson);
}
void pushup(llt rt){
    ST[rt].maxn=max(ST[lson].maxn,ST[rson].maxn);
}
void pushdown(llt rt){
    if(ST[rt].flag!=0){
        ST[lson].maxn+=ST[rt].flag;
        ST[lson].flag+=ST[rt].flag;
        ST[rson].maxn+=ST[rt].flag;
        ST[rson].flag+=ST[rt].flag;
        ST[rt].flag=0;
    }
}
void update(llt y1,llt y2,llt rt,llt val){
    if(y1<=Y[ST[rt].l]&&Y[ST[rt].r]<=y2){
        ST[rt].flag+=val;
        ST[rt].maxn+=val;
        return ;
    }
    pushdown(rt);
    llt mid=(ST[rt].l+ST[rt].r)>>1;
    if(y1<=Y[mid])update(y1,y2,lson,val);
    if(Y[mid]

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(线段树,扫描线)