zoukankan      html  css  js  c++  java
  • 扫描线(一)——求矩形面积并

    前言

    扫描线应该是一个很有用的算法。

    它有许多用途,比较经典的应该就是用来求矩形面积并

    什么是矩形面积并

    或许你会问,什么是矩形面积并?

    在一个平面上,有若干个矩形,它们覆盖的总面积就是矩形面积并(重叠部分只算一次)。

    要求矩形面积并,我们就可以用扫描线。

    离散化

    首先,我们要将这张图离散化预处理一下。

    离散化的过程应该比较简单,将每个节点的坐标全部存在一个数组中,排序+去重之后,就形成了一个离散化之后的数组,把它们加入一个(map)中即可(或直接二分)。

    代码如下:

    for(i=1;i<=n;++i) cin>>x1>>y1>>x2>>y2,xy[(i<<2)-3]=x1,xy[(i<<2)-2]=y1,xy[(i<<2)-1]=y2,xy[i<<2]=x2;//将每一个点的坐标全部存在一个数组中
    sort(xy+1,xy+(n<<2)+1);//排序
    for(i=1;i<=n<<2;++i)//枚举每一个值 
    	if(!p[xy[i]]) f[p[xy[i]]=++cnt]=xy[i];//p存储离散化后的值,f存储原来的值,需去重
    

    如何存储一个矩形

    接下来,我们要考虑如何存储一个读入的矩形。

    不难发现,其实我们只需要记录每个矩形的左右两条边即可,其中一条标记为正,一条标记为负,表示开始与结束。

    代码如下:

    struct Square//一个结构体
    {
        int flag,nx,ny1,ny2;//flag记录这条边的类型(1或-1,分别表示开始与结束),nx,ny1,ny2分别存储x,y1,y2离散化后的值
        double x,y1,y2;//x记录这条边x轴上的坐标,y1,y2分别记录这条边在y轴上的起点与终点
    }a[2*N+5];
    
    for(i=1;i<=n;++i) cin>>x1>>y1>>x2>>y2,a[(i<<1)-1]=(Square){1,0,0,0,x1,y1,y2},a[i<<1]=(Square){-1,0,0,0,x2,y1,y2};//读入并存储每一个矩形
    
    //离散化的过程(见上)
    
    for(i=1;i<=n<<1;++i)//将每条边的坐标更新为离散化后的坐标
    	a[i].nx=p[a[i].x],a[i].ny1=p[a[i].y1],a[i].ny2=p[a[i].y2];
            
    

    扫描线的核心过程

    做完了以上的一系列处理,我们就可以开始用扫描线来扫描了。

    • 首先,我们按照从左往右的顺序枚举每一条边。

    • 每当(x)坐标发生了变化,我们就要更新(ans)了,需将(ans)加上 (x)轴的变化量×当前被覆盖的长度

    • 对于当前操作的边,我们又分两种讨论:

      • 对于一条开始的边((flag=1)),我们就将这条边所覆盖的节点被覆盖次数加(1)
      • 对于一条结束的边((flag=-1)),我们就将这条边所覆盖的节点被覆盖次数减(1)

      综上所述,我们只需将这条边所覆盖的节点被覆盖次数加上(flag)即可。

    不难发现,在最坏情况下时间复杂度是(O(n^2))的。

    因此,就需要优化。

    线段树优化

    其实这题的优化也很简单,直接用线段树维护即可。

    对于每个节点,需要维护这个节点所代表的区间内被覆盖的长度(Sum[i])以及这个区间被覆盖的次数(Exist[i])

    不难发现,对于某一时刻,被覆盖的总长度应为(Sum[1])(因为(1)号节点代表的区间为([1...n])),因此我们只需写区间修改操作即可。

    代码如下:

    inline void PushUp(int l,int r,int rt)//从子节点上传信息
    {
        if(Exist[rt]) Sum[rt]=f[r+1]-f[l];//如果当前节点本身就被覆盖了,那么这个区间被覆盖的总长度就是f[r+1]-f[l]
        else if(l==r) Sum[rt]=0;//不然,如果这个区间只有一个节点,那么被覆盖的总长度为0
        else Sum[rt]=Sum[rt<<1]+Sum[rt<<1|1];//否则,被覆盖的总长度就等同于左右子节点被覆盖的总长度之和
    }
    inline void Update(int l,int r,int rt,int L,int R,int v)//线段树的区间修改
    {
        if(L>R) return;//如果修改区间的左边界大于右边界,就退出修改
        if(L<=l&&r<=R) return (void)(Exist[rt]+=v,PushUp(l,r,rt));//如果当前区间被修改区间包含,就更新当前节点信息
        register int mid=l+r>>1;
        if(L<=mid) Update(l,mid,rt<<1,L,R,v);//修改左儿子
        if(R>mid) Update(mid+1,r,rt<<1|1,L,R,v);//修改右儿子
        PushUp(l,r,rt);//从子节点上传信息
    }
    

    这样,就可以将代码时间复杂度降为(O(nlogn))了。

    代码

    #include<bits/stdc++.h>
    #define N 100
    using namespace std;
    int n,cnt,Exist[N<<4];
    double Sum[N<<4],xy[(N<<2)+5];
    struct Square//一个结构体
    {
        int flag,nx,ny1,ny2;//flag记录这条边的类型(1或-1,分别表示开始与结束),nx,ny1,ny2分别存储x,y1,y2离散化后的值
        double x,y1,y2;//x记录这条边x轴上的坐标,y1,y2分别记录这条边在y轴上的起点与终点
    }a[2*N+5];
    map<double,int> p;map<int,double> f;//p存储离散化后的值,f存储原来的值
    //线段树模板--------------------------------------------------------------------
    inline void PushUp(int l,int r,int rt)//从子节点上传信息
    {
        if(Exist[rt]) Sum[rt]=f[r+1]-f[l];//如果当前节点本身就被覆盖了,那么这个区间被覆盖的总长度就是f[r+1]-f[l]
        else if(l==r) Sum[rt]=0;//不然,如果这个区间只有一个节点,那么被覆盖的总长度为0
        else Sum[rt]=Sum[rt<<1]+Sum[rt<<1|1];//否则,被覆盖的总长度就等同于左右子节点被覆盖的总长度之和
    }
    inline void Update(int l,int r,int rt,int L,int R,int v)//线段树的区间修改
    {
        if(L>R) return;//如果修改区间的左边界大于右边界,就退出修改
        if(L<=l&&r<=R) return (void)(Exist[rt]+=v,PushUp(l,r,rt));//如果当前区间被修改区间包含,就更新当前节点信息
        register int mid=l+r>>1;
        if(L<=mid) Update(l,mid,rt<<1,L,R,v);//修改左儿子
        if(R>mid) Update(mid+1,r,rt<<1|1,L,R,v);//修改右儿子
        PushUp(l,r,rt);//从子节点上传信息
    }
    //----------------------------------------------------------------------------
    inline bool cmp(Square x,Square y)//比较两条边
    {
        return x.nx<y.nx;//返回x轴坐标较小的
    }
    int main()
    {
        register int i;register double x1,x2,y1,y2;
        for(i=1;i<=n;++i) cin>>x1>>y1>>x2>>y2,a[(i<<1)-1]=(Square){1,0,0,0,xy[(i<<2)-3]=x1,xy[(i<<2)-2]=y1,xy[(i<<2)-1]=y2},a[i<<1]=(Square){-1,0,0,0,xy[i<<2]=x2,y1,y2};
        //一个离散化的过程--------------------------------------------------------------------
        sort(xy+1,xy+(n<<2)+1);//排序
        for(i=1;i<=n<<2;++i)//枚举每一个值 
            if(!p[xy[i]]) f[p[xy[i]]=++cnt]=xy[i];
        for(i=1;i<=n<<1;++i)//将每条边的坐标更新为离散化后的坐标
            a[i].nx=p[a[i].x],a[i].ny1=p[a[i].y1],a[i].ny2=p[a[i].y2];
        //--------------------------------------------------------------------------------
        sort(a+1,a+(n<<1)+1,cmp),memset(Exist,0,sizeof(Exist)),memset(Sum,0,sizeof(Sum));
        int Now=1;double ans=0.0;//Now表示当前扫描到的边的编号,ans记录面积
        for(i=1;i<=cnt;++i)//枚举x坐标
        {
            ans+=(f[i]-f[i-1])*Sum[1];//更新ans
            if(a[Now].nx^i) continue;//如果当前边不在扫描到的这一列上,就跳过
            while(a[Now].nx==i&&Now<=n<<1)
                Update(1,cnt,1,a[Now].ny1,a[Now].ny2-1,a[Now].flag),++Now;//修改,操作下一条边
        }
        return printf("%.2lf",ans),0;
    }
    

    例题

    【HDU1542】Atlantis

  • 相关阅读:
    0902-用GAN生成动漫头像
    0901-生成对抗网络GAN的原理简介
    AES加密
    排序问题
    js中0.1+0.2!=0.3的问题
    关于JavaScript中Number整数最大长度的一个疑问
    IEEE 754标准
    关于浏览器接口Preview中的数值和postman中获取到的不一致问题
    .Net Core 配置之long类型 前端精度丢失和时间格式设置
    .netcore GRPC根据协议生成代码,以及去掉非空判断
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/ScanningLine_RectangleArea.html
Copyright © 2011-2022 走看看