亚特兰蒂斯(线段树+扫描线:求矩形面积并)

有几个古希腊书籍中包含了对传说中的亚特兰蒂斯岛的描述。

其中一些甚至包括岛屿部分地图。

但不幸的是,这些地图描述了亚特兰蒂斯的不同区域。

您的朋友Bill必须知道地图的总面积。

你自告奋勇写了一个计算这个总面积的程序。

输入格式

输入包含多组测试用例。

对于每组测试用例,第一行包含整数n,表示总的地图数量。

接下来n行,描绘了每张地图,每行包含四个数字x1,y1,x2,y2x1,y1,x2,y2(不一定是整数),(x1,y1)(x1,y1)和(x2,y2)(x2,y2)分别是地图的左上角位置和右下角位置。

注意,坐标轴 x 轴从上向下延伸,y 轴从左向右延伸。

当输入用例n=0时,表示输入终止,该用例无需处理。

输出格式

每组测试用例输出两行。

第一行输出”Test case #k”,其中k是测试用例的编号,从1开始。

第二行输出“Total explored area: a”,其中a是总地图面积(即此测试用例中所有矩形的面积并,注意如果一片区域被多个地图包含,则在计算总面积时只计算一次),精确到小数点后两位数。

在每个测试用例后输出一个空行。

数据范围

1≤n≤10000,
0≤x1<x2≤100000,
0≤y1<y2≤100000
注意,本题 nn 的范围上限加强至 10000。

输入样例:

2
10 10 20 20
15 15 25 25.5
0

输出样例:

Test case #1
Total explored area: 180.00 

样例解释

样例所示地图覆盖区域如下图所示,两个矩形区域所覆盖的总面积,即为样例的解。

无标题.png

题意:给定n个矩形的对角坐标,求n个矩形的面积并

思路(线段树+扫描线+离散化):

扫描线的基本思路:

就是用一条线从图形的一边扫过整个图形到另一边,在此过程中,可用一个变量vis来标记当前矩形的底边和顶边,分别记为1和-1,这样就可以通过判断vis,轻松地在扫到底边时统计上当前边长,在扫过当前矩形时(即:扫到当前矩形的顶边),去掉当前边长对当前统计的边长的影响,只需要+vis即可实现。

首先,用扫描线依次从x轴向右扫描(如样例所示),可以发现通过扫描可以将整个图形分割成若干个子矩形,矩形的底边长是在不断变化的,此过程可以忽略矩形的高,只看底边,可以用线段树来维护当前子矩形(即一段区间内)的底边长(即:当前扫过的线段覆盖在(平行于)x轴的线段的总长度),然后利用前缀和的思想,在记录每条线段时 ,记录其到x轴的长度(即为:当前线段的高h),然后通过作差即可得到两线段之间的高(即为:当前子矩形的高),最终把这些子矩形的面积加起来即为最终矩形面积并。

线段树维护两个量:1、vis:当前结点在扫描线覆盖的线段个数

                                 2、len:以当前结点为根的子树中覆盖在x轴的总长度(当前矩形的底边长)

注:因为按输入顺序,矩形的位置可能是无序的,为了便于扫描线依次扫过全部矩形,所以先按矩形高排序

       坐标值较大,所以要对底边坐标进行离散化,即:排序去重二分映射到其下标值,将离散化后的值带入线段树维护的区间端点值,然后用到真值时,通过把当前值代入离散化数组的下标即可得到数组的值即为原来的值。

完整代码:

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int maxn=2e4+5;

struct line
{
    double l,r,h;
    int vis;
    bool operator<(const line &t) const
    {
        return h<t.h;
    }
}e[maxn];

struct tree
{
    int vis;
    double len;
}tr[maxn*8];

int n,cnt;
double x[maxn];
double a,b,c,d;

void pushdown(int p,int l,int r)
{
    if(tr[p].vis) tr[p].len=x[r+1]-x[l];//因为这里线段树维护的是一个个的区间[L,L+1],所以r+1
    else if(l==r) tr[p].len=0;
    else tr[p].len=tr[p<<1].len+tr[p<<1|1].len;
}

void update(int p,int l,int r,int L,int R,int v)//当前根节点p,用来更新的区间[l,r],当前更新区间[L,R],当前线段的标记量v:vis
{
    if(L>=l&&R<=r){
        tr[p].vis+=v;
        pushdown(p,L,R);
        return;
    }
    int mid=L+R>>1;
    if(l<=mid) update(p<<1,l,r,L,mid,v);
    if(r>mid) update(p<<1|1,l,r,mid+1,R,v);
    pushdown(p,L,R);
}

int main()
{
    int t=0;
    while(cin>>n,n){
        memset(tr,0,sizeof tr);
        cnt=0;
        for(int i=0;i<n;i++){
            cin>>a>>b>>c>>d;
            x[cnt]=a;
            e[cnt++]={a,c,b,1};
            x[cnt]=c;
            e[cnt++]={a,c,d,-1};
        }
        //对底边进行离散化:
        sort(x,x+cnt);
        int xs=unique(x,x+cnt)-x;
        //对底边按高排序:
        sort(e,e+cnt);
        double res=0;
        //扫描线从x轴往外依次扫描:
        for(int i=0;i<cnt;i++){
            //当前底边长[pl,pr]
            int pl=lower_bound(x,x+xs,e[i].l)-x;
            int pr=lower_bound(x,x+xs,e[i].r)-x-1;//因为一个结点维护的是一个区间,所以区间序号为L,R为区间序号+1
            update(1,pl,pr,0,xs,e[i].vis);
            res+=tr[1].len*(e[i+1].h-e[i].h);//当前矩形面积=(当前矩形底边长:线段树维护的当前覆盖在x轴上的总长度)*(当前矩形高度:e[i+1].h-e[i].h)
        }
        printf("Test case #%d\n",++t);
        printf("Total explored area: %.2f\n",res);
        cout<<endl;
    }
    return 0;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值