矩形面积并(POJ 1151)

problem

给你N个矩形,求这些矩阵的面积并 N 1e5

思路

“扫描线思路”+离散化+线段树维护

关于扫描线可以看下这篇博客
关于离散化

由于坐标范围较大,需要使其更“紧密”,这样才能用线段树处理

离散化的核心思想就是 相对大小不变 且能通过现在的值确定之前的值

所以离散化的方法就是(以y轴坐标为例) 将y轴坐标塞入id数组(2*n个) 排序

将之前每个事件的y轴坐标替换为在id数组中的下标 (二分去找) 这样就完成了离散化

关于线段树维护

将事件分成两类,即入事件和出事件。

对于入事件,我们将区间cov值+1,即对应区间标记+1

对于出事件,我们将区间cov值-1 ,即对应区间标记-1

每一次处理过一个事件后,对于总区间,我们维护的sum[1]的值为那些标记不为0的区间所对应的长度,这些长度可以作为面积并的长,高即为 evt[i+1].hevt[i].h

当再处理一个事件时,如果它是出事件,那么自然会更新对应区间cov的值-1,剩下的不为0的区间又可以作为面积并的长,以此类推……

两种不同的写法

注意到,这里处理的是区间,即最小单位是长度为1的区间(离散化后)。但是一般的线段树其叶子节点就表示那个点,而不是区间。

举个例子,比如[3,4]区间+1,一般写法会将叶子节点3和叶子节点4的值+1,再pushup。但这里我们没法处理这样跨结点的区间,因为要么两边都加(超了),要么都return (少了)。即对于点线段树递归区间时要采用左闭右开的方法。

所以有两种写法:

  • ①点线段树,需要左闭右开处理
  • ②区间线段树,叶子节点可表示区间,需要改变递归区间的边界
下面是两种方法的代码实现,细节见代码注释

代码示例1(点线段树)

//沿y轴方向扫描  对x轴坐标离散化后用线段树维护
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1//这里叶子节点还是表示点而不是区间,这也是通常的做法
using namespace std;
typedef long long ll;
const int maxn=100010*2;//注意"事件是矩形数量的两倍"

int n;
int cov[maxn<<2];//区间覆盖情况
double id[maxn],sum[maxn<<2];//id用于离散化

struct event{
    double l,r,h;//h为纵坐标,扫描线平行于x轴
    int f;
    event(){}//必须要加
    event(double l,double r,double h,int f):l(l),r(r),h(h),f(f){}
    bool operator < (const event & rhs) const{//按照h进行排序
        return h < rhs.h;
    }
}evt[maxn];

void pushup(int l,int r,int rt){
    //l r这个区间对应的rt  若cov[rt]!=0 也即>=1  当然其不可能<0
    if(cov[rt]) sum[rt]=id[r+1]-id[l];//左闭右开  注意是r+1
    else if(l==r) sum[rt]=0;//注意点是没有长度的
    else sum[rt]=sum[rt<<1]+sum[rt<<1|1];//不是叶子节点,从下往上更新
}

void update(int L,int R,int c,int l,int r,int rt){
    if(L<=l && r<=R){//当前节点区间在目的区间内
        cov[rt]+=c;
        pushup(l,r,rt);
        return ;
    }
    int m=(l+r)>>1;
    if(L<=m) update(L,R,c,lson);
    if(R>m) update(L,R,c,rson);
    pushup(l,r,rt);
}

int main()
{
    int cas=1;
    while(~scanf("%d",&n),n){
        for(int i=1;i<=n;++i){
            double x1,y1,x2,y2;//左上和右下
            scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
            evt[i]=event(x1,x2,y1,-1);
            evt[i+n]=event(x1,x2,y2,1);
            id[i]=x1;
            id[i+n]=x2;
        }
        sort(evt+1,evt+2*n+1);
        sort(id+1,id+2*n+1);
        int m=unique(id+1,id+2*n+1)-id-1;//其实可以不去重 
        memset(cov,0,sizeof(cov));
        memset(sum,0,sizeof(sum));
        double ans=0;
        for(int i=1;i<2*n;i++){
            int l=lower_bound(id+1,id+m+1,evt[i].l)-id;//注意线段树下标从1开始
            int r=lower_bound(id+1,id+m+1,evt[i].r)-id;
            if(l<=(r-1)) update(l,r-1,evt[i].f,1,m,1);//注意是r-1  套路
            ans+=sum[1]*(evt[i+1].h-evt[i].h);//sum[1]记录了当前的“有效长度”
        }
        printf("Test case #%d\nTotal explored area: %.2f\n\n",cas++, ans);
    }
    return 0;
}


代码示例2(区间线段树) 注意第7行 、第39行

//1是沿y轴方向扫描  对x轴坐标离散化后用线段树维护
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define lson l,m,rt<<1
#define rson m,r,rt<<1|1//这里叶子节点表示最小区间
using namespace std;
typedef long long ll;
const int maxn=100010*2;//注意"事件是矩形数量的两倍"

int n;
int cov[maxn<<2];//区间覆盖情况
double id[maxn],sum[maxn<<2];//id用于离散化

struct event{
    double l,r,h;//h为纵坐标,扫描线平行于x轴
    int f;
    event(){}//必须要加
    event(double l,double r,double h,int f):l(l),r(r),h(h),f(f){}
    bool operator < (const event & rhs) const{//按照h进行排序
        return h < rhs.h;
    }
}evt[maxn];

void pushup(int l,int r,int rt){
    //l r这个区间对应的rt  若cov[rt]!=0 也即>=1  当然其不可能<0
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    if(cov[rt]) sum[rt]=id[r]-id[l];//注意是r就行  因为是区间线段树 比较自然
}

void update(int L,int R,int c,int l,int r,int rt){
    if(L<=l && r<=R){//当前节点区间在目的区间内
        cov[rt]+=c;
        pushup(l,r,rt);
        return ;
    }
    int m=(l+r)>>1;
    if(L<m) update(L,R,c,lson);//注意这里是< 没有等于号
    if(R>m) update(L,R,c,rson);
    pushup(l,r,rt);
}

int main()
{
    int cas=1;
    while(~scanf("%d",&n),n){
        for(int i=1;i<=n;++i){
            double x1,y1,x2,y2;//左上和右下
            scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
            evt[i]=event(x1,x2,y1,-1);
            evt[i+n]=event(x1,x2,y2,1);
            id[i]=x1;
            id[i+n]=x2;
        }
        sort(evt+1,evt+2*n+1);
        sort(id+1,id+2*n+1);
        int m=unique(id+1,id+2*n+1)-id-1;//其实可以不去重
        memset(cov,0,sizeof(cov));
        memset(sum,0,sizeof(sum));
        double ans=0;
        for(int i=1;i<2*n;i++){
            int l=lower_bound(id+1,id+m+1,evt[i].l)-id;//注意线段树下标从1开始
            int r=lower_bound(id+1,id+m+1,evt[i].r)-id;
            update(l,r,evt[i].f,1,m,1);//注意是r  比较自然
            ans+=sum[1]*(evt[i+1].h-evt[i].h);//sum[1]记录了当前的“有效长度”
        }
        printf("Test case #%d\nTotal explored area: %.2f\n\n",cas++, ans);
    }
    return 0;
}

这里都是沿y轴方向扫描 对x轴坐标离散化后用线段树维护的;当然也可以反过来

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值