HDU 1255 覆盖的面积 线段树+扫描线

覆盖的面积Time Limit:5000MS    Memory Limit:32768KB    64bit IO Format:%I64d & %I64u
SubmitStatus

Description

给定平面上若干矩形,求出被这些矩形覆盖过至少两次的区域的面积.

HDU 1255 覆盖的面积 线段树+扫描线_第1张图片

 

Input

输入数据的第一行是一个正整数T(1<=T<=100),代表测试数据的数量.每个测试数据的第一行是一个正整数N(1<=N&lt;=1000),代表矩形的数量,然后是N行数据,每一行包含四个浮点数,代表平面上的一个矩形的左上角坐标和右下角坐标,矩形的上下边和X轴平行,左右边和Y轴平行.坐标的范围从0到100000.

注意:本题的输入数据较多,推荐使用scanf读入数据.
 

Output

对于每组测试数据,请计算出被这些矩形覆盖过至少两次的区域的面积.结果保留两位小数.
 

Sample Input

     
     
     
     
2 5 1 1 4 2 1 3 3 7 2 1.5 5 4.5 3.5 1.25 7.5 4 6 3 10 7 3 0 0 1 1 1 0 2 1 2 0 3 1

Sample Output

     
     
     
     
7.63 0.00 一道线段树的经典题目。 要解决这个问题,必须要知道如何用线段树求矩形面积并,可以在我的这篇博客中看一下, 当然也可以自己搜索啦。http://blog.csdn.net/wr132/article/details/49797279 那么,当我们已经知道了如何求矩形面积并,问题已经解决了一半,因为求矩形面积并的 问题可以抽象为 求被覆盖了一次及一次以上的区域的面积;而我们的问题是 求被覆盖了两次及两次以上的区域的面积 这样大家就可一看出两道题目的共通之处了,接下来要解决浮点数的问题,我们在计算 矩形面积的时候,需要计算出x轴区域内被覆盖了两次及两次以上的区域的长度,并且这个 长度随着边的加入和删除是要实时更新的,而更新需要使用到线段树,关键是线段树的区间 得是整数,那么我们就需要对x轴做离散化,将浮点的坐标点映射为连续整数坐标点,这里 要注意一下坐标点的含义,它并不表示一个单纯的点,而是一个从该店开始,到下一个点 的一段区间,因为这个连续的区间是不可再分的,因此我们称其元区间 ,这些区间处于线段树 中的叶子的位置。 下面我再来看线段树。首先,其中的变量有l,r即当前节点的左右区间端点,以及 cnt,这是一个很重要的变量,因为我们是以所有矩形的边为基础进行离散化的,因此在输入 边的时候,就会出现这条边的一部分恰好完全覆盖这个区间的情况,这时候cnt就会相应 的增加或者减少,也就是说,cnt含义是恰好覆盖这个区间的边的数量。还有一个数组 len[2],len[0]表示该区间中覆盖次数大于0次的区域的长度,len[1]表示该区域中覆盖 次数大于1次的区域的长度,这是我们计算的关键,因为我们最终要获得的就是根节点的 len[1],即整个区间中覆盖次数大于1次的区域的长度。那么len到底怎么计算呢? 对于len[0] 1.if当前节点的cnt大于0,那么这个区间内必定包含至少一条完全覆盖区间的边, 因此tr[rt].len[0]=pos[tr[rt].r+1]-pos[tr[rt].l],这里注意,为什么 要把右区间加1,还记得我们说叶子节点也表示一个区间,是从这个点开始到下一个点,也就是 说区间端点是左闭右开的,最右边的点是不包含的,那是因为区间数要比点数少1。 2.else if当前节点是叶子节点,那么这个节点的len[0]肯定是0,因为没有比它 更短的区间了。 3.else,不知道当前节点的覆盖状态,只能由其两个子区间的len[0]值的和来决定。 对于len[1] 1.if当前节点的cnt大于1, 那么这个区间内必定包含至少两条完全覆盖区间的边, 因此len[1]可以直接就等于这个区间的长度 2.else if当前节点是叶子节点,那么其len[1]必定为0,因为没有比它更小的区间 可以帮它覆盖两次以上。 3.else if当前节点的cnt==1,那么我们可以确定一定有一条边完全覆盖了这个区间, 那么只要存在一条任意长度的边出现在这个区间内,就可以和那条长边构成覆盖两次及以上, 因此我们只需要去看看左右孩子的len[0],把他们相加就是该节点的len[1]。 4.else 当前节点的cnt==0,也就是说这个大区域的覆盖情况未知,那没办法,只能缩小 面积,查看子区间的len[1],将其相加即可得到这个大区间的len[1]。 当以上的工作都准备完成后,我们就可以计算面积了,方法很简单,每一次向线段树中插入一条边, 更新线段树,并且由根节点的len[1]值和相邻两条线段的高度差计算出一个小矩形的面积, 将面积累加,不要忘了最后保留两位小数。
#include <iostream>
#include <stdio.h>
#include <algorithm>
#define maxn 1010
#define lson(rt) rt<<1
#define rson(rt) rt<<1|1
using namespace std;

struct segment//保存没条线段的信息
{
    double l,r,h;
    int v;
};

struct node//保存线段树中的节点
{
    int l,r;
    int cnt;//表示与这个区间完全吻合的线段的个数
    double len[2];
};
double pos[maxn<<1];
segment s[maxn<<1];
node tr[maxn<<3];


bool cmp(const segment &a,const segment &b)
{
    return a.h<b.h;
}


void push_up(int rt)
{
    //更新覆盖1次及1次以上的区域的长度
    if(tr[rt].cnt)
        tr[rt].len[0]=pos[tr[rt].r+1]-pos[tr[rt].l];
    else if(tr[rt].l==tr[rt].r)
        tr[rt].len[0]=0;
    else
        tr[rt].len[0]=tr[lson(rt)].len[0]+tr[rson(rt)].len[0];
    //更新覆盖2次及2次以上区域的长度
    if(tr[rt].cnt>1)
        tr[rt].len[1]=pos[tr[rt].r+1]-pos[tr[rt].l];
    else if(tr[rt].l==tr[rt].r)
        tr[rt].len[1]=0;
    else if(tr[rt].cnt==1)
        tr[rt].len[1]=tr[lson(rt)].len[0]+tr[rson(rt)].len[0];
    else
        tr[rt].len[1]=tr[lson(rt)].len[1]+tr[rson(rt)].len[1];
}


void build(int l,int r,int rt)
{
    tr[rt].l=l;tr[rt].r=r;
    tr[rt].cnt=tr[rt].len[0]=tr[rt].len[1]=0;
    if(l==r)
        return;
    int m=(tr[rt].l+tr[rt].r)>>1;
    build(l,m,lson(rt));
    build(m+1,r,rson(rt));
}


int bin_search(double val,int l,int r)//二分查找
{
    int m;

    while(l<=r)
    {
        m=(l+r)>>1;
        if(pos[m]==val)
        {
            //printf("m=%d\n",m);
            return m;
        }
        if(val<=pos[m])
            r=m;
        else
            l=m+1;
    }
    return -1;
}

void update(int l,int r,int v,int rt)
{
    if(tr[rt].l==l&&tr[rt].r==r)
    {
        tr[rt].cnt+=v;
        push_up(rt);
        return;
    }
    int m=(tr[rt].l+tr[rt].r)>>1;
    if(r<=m)
        update(l,r,v,lson(rt));
    else if(l>m)
        update(l,r,v,rson(rt));
    else
    {
        update(l,m,v,lson(rt));
        update(m+1,r,v,rson(rt));
    }
    push_up(rt);
}

int main()
{
    int t,n,p,i,l,r;
    double x1,y1,x2,y2;
    double res;
    scanf("%d",&t);
    while(t--)
    {
        res=0;
        p=0;
        scanf("%d",&n);
        while(n--)
        {
            scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
            pos[p]=x1;
            pos[p+1]=x2;
            s[p].l=s[p+1].l=x1;
            s[p].r=s[p+1].r=x2;
            s[p].h=y1;
            s[p+1].h=y2;
            s[p].v=1;
            s[p+1].v=-1;
            p+=2;
        }
        sort(pos,pos+p);
        sort(s,s+p,cmp);
        int m=1;
        for(i=1;i<p;i++)//去除相同的x值
            if(pos[i]!=pos[i-1])
                pos[m++]=pos[i];
        build(0,m-1,1);
        for(i=0;i<p-1;i++)//最后一条边一定是顶边,不需要插入
        {
            l=bin_search(s[i].l,0,m-1);
            r=bin_search(s[i].r,0,m-1)-1;//注意这里的区间端点是前闭后开
            //printf("l=%d,r=%d\n",l,r);
            update(l,r,s[i].v,1);
            res+=tr[1].len[1]*(s[i+1].h-s[i].h);
        }
        res+=0.004;
        printf("%.2f\n",res);
    }
    return 0;
}



 

你可能感兴趣的:(线段树,HDU,扫描线,覆盖的面积,1255)