POJ1151Atlantis 矩形面积并 扫描线 线段树

欢迎访问~原文出处——博客园-zhouzhendong

去博客园看该题解


题目传送门 - POJ1151


题意概括

  给出n个矩形,求他们的面积并。

  n<=100


 

题解

  数据范围极小。

  我们分3种算法逐步优化。

 

  算法1: O(n3)

  如果这n个矩形的坐标都是整数,而且比较小,那么我们显然可以用最暴力的方法:一个一个打标记。

  但是不是这样的。

  坐标大小很大,而且是实数。

  然而我们发现差不多,只要先离散化一下,然后再打标记即可。

 

  算法2:O(n2)

  实际上,上面的方法十分慢。如果n的范围到了1000,上面的就无济于事了。

  而实际上,基于上面的打标记的算法,我们可以通过差分的方法n2解决。

  我们通过差分,可以用n2的时间标记,n2的时间判断每一个区域是否被覆盖。

  空间复杂度O(n2)

 

  算法3:O(n logn) 扫描线

  实际上,这类问题的数据范围可以到100000这个级别。

  矩形面积并可以用扫描线算法来解决。先看原理,后面讲具体实现。

  比如下图:

  

  当前我们的扫描线到达了淡黄色部分。

  由于之前没有记录,所以答案不增加。

  然而我们记下当前横向覆盖的长度。

  然后我们到了第二条扫描线,加上原来记录的横向覆盖长度乘以增加的高度就是当前增加的答案。

  然后,我们更新了横向覆盖的长度。

  继续。

  

  然后第三条。现在的横向覆盖长度是两边加起来,所以增加的面积是两块了。

  然后更新横向覆盖的长度,加上了中间的那一条。

  然后继续。

  

  现在有这么长的一条都是被横向覆盖的了。

  所以新增的面积是浅蓝色部分。

  然后我们发现左上那条线是出边,所以要删除这一条线。

  所以横向覆盖的长度为如下:

  

  同理,接下来是:

  

  然后就OK了。

  

  那么具体怎么实现呢?

  我们开一棵线段树来维护!

  在读入之后,我们把所有的横线都拆开,分成下边和上边两类。某一区间在进入下边的时候+1,离开上边的时候-1,所以我们分别给上下边标记+1和-1。

  对于Y,我们离散化一下。

  对于X,我们按照边的X排一个序。

  然后按照刚才那样的处理。

  具体如何维护详见代码。


 

代码

#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
using namespace std;
const int N=100+5,M=N*2;
const double Eps=1e-9;
int T=0,n,m,tot_Y,tot_s;
double Y[M];
struct Segment{
    double x,L,R;
    int v;
    void set(double x_,double L_,double R_,int v_){
        x=x_,L=L_,R=R_,v=v_;
    }
}s[M];
struct SegTree{
    int cnt;
    double sum;
}t[M*4];
bool cmp_s(Segment a,Segment b){
    return a.x<b.x;
}
void build(int rt,int le,int ri){
    t[rt].cnt=0;
    t[rt].sum=0;
    if (le==ri)
        return;
    int mid=(le+ri)>>1,ls=rt<<1,rs=ls|1;
    build(ls,le,mid);
    build(rs,mid+1,ri);
}
void pushup(int rt,int le,int ri){
    int ls=rt<<1,rs=ls|1;
    if (t[rt].cnt)
        t[rt].sum=Y[ri+1]-Y[le];
    else if (le==ri)
        t[rt].sum=0;
    else
        t[rt].sum=t[ls].sum+t[rs].sum;
}
void update(int rt,int le,int ri,int xle,int xri,int d){
    if (le>xri||ri<xle)
        return;
    if (xle<=le&&ri<=xri){
        t[rt].cnt+=d;
        pushup(rt,le,ri);
        return;
    }
    int mid=(le+ri)>>1,ls=rt<<1,rs=ls|1;
    update(ls,le,mid,xle,xri,d);
    update(rs,mid+1,ri,xle,xri,d);
    pushup(rt,le,ri);
}
int find_double(double x){
    int le=1,ri=m,mid;
    while (le<=ri){
        mid=(le+ri)>>1;
        if (abs(x-Y[mid])<Eps)
            return mid;
        if (Y[mid]<x)
            le=mid+1;
        else
            ri=mid-1;
    }
}
int main(){
    while (scanf("%d",&n)&&n){
        tot_Y=tot_s=0;
        for (int i=1;i<=n;i++){
            double xA,yA,xB,yB;
            scanf("%lf%lf%lf%lf",&xA,&yA,&xB,&yB);
            if (yB-yA<Eps||xB-xA<Eps)
                continue;
            Y[++tot_Y]=yA,Y[++tot_Y]=yB;
            s[++tot_s].set(xA,yA,yB,1);
            s[++tot_s].set(xB,yA,yB,-1);
        }
        sort(Y+1,Y+tot_Y+1);
        sort(s+1,s+tot_s+1,cmp_s);
        m=1;
        for (int i=2;i<=tot_Y;i++)
            if (Y[i]-Y[i-1]>Eps)
                Y[++m]=Y[i];
        build(1,1,m);
        double ans=0;
        for (int i=1;i<=tot_s;i++){
            ans=ans+(s[i].x-s[i-1].x)*t[1].sum;
            int L=find_double(s[i].L);
            int R=find_double(s[i].R);
            update(1,1,m,L,R-1,s[i].v);
        }
        printf("Test case #%d\nTotal explored area: %.2lf\n\n",++T,ans);
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/zhouzhendong/p/POJ1151.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值