【poj1151】Atlantis(矩形面积并+线段树+扫描线)

Description

    There are several ancient Greek texts that contain descriptions of the fabled island Atlantis. Some of these texts even include maps of parts of the island. But unfortunately, these maps describe different regions of Atlantis. Your friend Bill has to know the total area for which maps exist. You (unwisely) volunteered to write a program that calculates this quantity.

Input

    The input consists of several test cases. Each test case starts with a line containing a single integer n (1 <= n <= 100) of available maps. The n following lines describe one map each. Each of these lines contains four numbers x1;y1;x2;y2 (0 <= x1 < x2 <= 100000;0 <= y1 < y2 <= 100000), not necessarily integers. The values (x1; y1) and (x2;y2) are the coordinates of the top-left resp. bottom-right corner of the mapped area.
    The input file is terminated by a line containing a single 0. Don’t process it.

Output

     For each test case, your program should output one section. The first line of each section must be “Test case #k”, where k is the number of the test case (starting with 1). The second one must be “Total explored area: a”, where a is the total explored area (i.e. the area of the union of all rectangles in this test case), printed exact to two digits to the right of the decimal point.
Output a blank line after each test case.

Sample Input

2
10 10 20 20
15 15 25 25.5
0

Sample Output

Test case #1
Total explored area: 180.00 

I think

    题目大意:在一个坐标轴内分别给出n个矩形的左下角与右上角横纵坐标,求所有矩形的面积并。(题目描述与样例不符,实践证明以样例为准)
    算法:线段树+扫描线+离散化
    思想:想象有一条平行于y轴自由移动的直线,它遇到第一条矩形的入边,就记下其长度d,接着继续扫。遇到第二条边,无论是入边还是出边,都先用两条边横坐标的距离×d更新ans,然后更新之前记下的长度,如果是入边,则长度可能增加(考虑相互覆盖的情况),反之长度也可能减小。
    同理对于扫描线平行于x轴,横坐标离散化即可。
    实现:将所有纵坐标离散化,两两纵坐标之间的线段用线段树存储。对于矩形每一条平行于y轴的边,用结构体line储存下其横坐标x,上下端y1,y2,以及这条边是入边(flag=1)还是出边(flag=-1)。按照x从小到大对line排序。从左至右扫描时每次用当前line的 flag值与用以记覆盖值的c[]结合使用实现y方向线段长度的更新。
    写之前要仔细考虑好数组大小,多组数据时需要每次更新/清空的变量,以及每一个变量的意义。

Code

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

const int sm = 250;

int T,n,t,f;
int c[sm<<2];
double x1,x2,y1,y2,ans;
double y[sm<<2],val[sm<<2];
struct line {
    double x,y1,y2;
    int flag;
}a[sm];
bool cmp(line u,line v) { return u.x<v.x; }
void build(int l,int r,int k) {
    if(l+1==r) {//线段树的叶子节点存的是两两纵坐标间的线段,因此不是l==r
        val[k]=c[k]=0;
        return;
    }
    int m=(l+r)>>1,ls=k<<1,rs=k<<1|1;
    build(l,m,ls),build(m,r,rs);//注意不是m+1,同理
    val[k]=c[k]=0;
}
void calen(int x,int l,int r) {//计算被覆盖区间的长度
    if(c[x]>0) val[x]=y[r]-y[l];
    else if(l+1==r) val[x]=0;
    else val[x]=val[x<<1]+val[x<<1|1];
    //c[x]的标记没有下传 因此可能出现c[x]==0 而包含于x区间的子区间中有c[i]>0
    //如首先添加一段线段a,再添加一段包含a的线段b
    //令线段b在线段a之前被删去,那么整个b所在区间c[b]==0
    //但是此时线段a还没有被删去,其所在区间c[a]!=0
}
void update(int l,int r,int k,line ll) {
    if(ll.y1<=y[l]&&y[r]<=ll.y2) {
        c[k]+=ll.flag;
        calen(k,l,r);
        return;
    }
    if(l+1==r)return;
    int m=(l+r)>>1,ls=k<<1,rs=k<<1|1;
    if(ll.y1<=y[m])update(l,m,ls,ll);
    if(ll.y2>=y[m])update(m,r,rs,ll);
    calen(k,l,r);
} 
int main() {
    while(scanf("%d",&n)!=EOF) {
        if(!n)break;
        ans=0;t=0;
        for(int j=1;j<=n;++j) {
            scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
            y[++t]=y1,a[t].x=x1,a[t].y1=y1,a[t].y2=y2,a[t].flag=1;
            y[++t]=y2,a[t].x=x2,a[t].y1=y1,a[t].y2=y2,a[t].flag=-1; 
        }
        sort(y+1,y+t+1);
        sort(a+1,a+t+1,cmp);
        build(1,t,1);
        update(1,t,1,a[1]);
        for(int j=2;j<=t;++j) {
            ans+=(a[j].x-a[j-1].x)*val[1];
            update(1,t,1,a[j]);
        }
        printf("Test case #%d\nTotal explored area: %.2f\n\n",++T,ans);
        //在poj上%.2f才能AC
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值