HDU1542-Atlantis(线段树+扫描线——面积并)

Atlantis

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 12947    Accepted Submission(s): 5401

Problem 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 file 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

大神的分析: 
1.矩形比较多,坐标也很大,所以横坐标需要离散化(纵坐标不需要),熟悉离散化后这个步骤不难,所以这里不详细讲解了,不明白的还请百度

2.重点:扫描线法:假想有一条扫描线,从左往右(从右往左),或者从下往上(从上往下)扫描过整个多边形(或者说畸形。。多个矩形叠加后的那个图形)。如果是竖直方向上扫描,则是离散化横坐标,如果是水平方向上扫描,则是离散化纵坐标。下面的分析都是离散化横坐标的,并且从下往上扫描的

扫描之前还需要做一个工作,就是保存好所有矩形的上下边,并且按照它们所处的高度进行排序,另外如果是上边我们给他一个值-1,下边给他一个值1,我们用一个结构体来保存所有的上下边

struct edge
{ 
double l,r,h; //l,r表示这条上下边的左右坐标,h是这条边所处的高度 
int f; //所赋的值,1或-1 
}

接着扫描线从下往上扫描,每遇到一条上下边就停下来,将这条线段投影到总区间上(总区间就是整个多边形横跨的长度),这个投影对应的其实是个插入和删除线段操作。还记得给他们赋的值1或-1吗,下边是1,扫描到下边的话相当于往总区间插入一条线段,上边-1,扫描到上边相当于在总区间删除一条线段(如果说插入删除比较抽象,那么就直白说,扫描到下边,投影到总区间,对应的那一段的值都要增1,扫描到上边对应的那一段的值都要减1,如果总区间某一段的值为0,说明其实没有线段覆盖到它,为正数则有,那会不会为负数呢?是不可能的,可以自己思考一下)。

每扫描到一条上下边后并投影到总区间后,就判断总区间现在被覆盖的总长度,然后用下一条边的高度减去当前这条边的高度,乘上总区间被覆盖的长度,就能得到一块面积,并依此做下去,就能得到最后的面积

(这个过程其实一点都不难,只是看文字较难体会,建议纸上画图,一画即可明白,下面献上一图希望有帮助) 
这里写图片描述

从这个图,也可以感受到,就好比一个畸形的容器,往里面倒水,从最下面往上面涨,被水淹过的部分其实就是我们要求的面积


大致思路是这样,但有很多坑点,没有注释的话看源码也要很久

具体实现:

1.首先存下每一条线段,按高度从小到大排序,然后存下每一个横坐标,去重后从小到大排序

2.更新区间:每次从排序好的横坐标中找出需要更新的区间的开头和结尾(此处有坑点,具体看代码),由于标记了上下边,每次加入下边标记+1,相当于加入一段边,再加入上边,标记-1,也就相当于清除,然后直接求面积即可

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int inf=0x3f3f3f3f;
const int N=220;
struct edge
{
    double l,r,h;
    int f;
    edge(){}
    edge(double x1,double x2,double y,double op)
    {
        l=x1,r=x2,h=y,f=op;
    }
    bool operator < (const edge&A)const
    {
        return h<A.h;
    }
}e[N];
struct node
{
   int cnt;
   double len;
}t[N<<2];
double pos[N];
void pushdown(int rt,int le,int ri)
{
    if(t[rt].cnt)//全部覆盖
        t[rt].len=pos[ri+1]-pos[le];
    else if(le==ri)//为一个点,长度为0
        t[rt].len=0;
    else//既不全部覆盖,也不是点,由两边区间得出
        t[rt].len=t[rt<<1].len+t[rt<<1|1].len;

}
void update(int x,int y,int l,int r,int rt,int val)
{
    if(x<=l&&y>=r)
    {
        t[rt].cnt+=val;
        pushdown(rt,l,r);
        return;
    }
    int m=(l+r)>>1;
    if(x<=m)
        update(x,y,l,m,rt<<1,val);
    if(y>m)
        update(x,y,m+1,r,rt<<1|1,val);
    pushdown(rt,l,r);
}
int main()
{
   int n,q=0;
   double x1,x2,y1,y2;
   while(~scanf("%d",&n)&&n)
   {
       mem(t,0);
       int tot=0;
       for(int i=0;i<n;i++)
       {
           scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
           pos[tot]=x1;
           e[tot++]=edge(x1,x2,y1,1);
           pos[tot]=x2;
           e[tot++]=edge(x1,x2,y2,-1);
       }
       sort(pos,pos+tot);
       sort(e,e+tot);
       int m=unique(pos,pos+tot)-pos;
       double ans=0;
       for(int i=0;i<tot;i++)
       {
           int x=lower_bound(pos,pos+m,e[i].l)-pos;
           int y=lower_bound(pos,pos+m,e[i].r)-pos-1;
           update(x,y,0,m,1,e[i].f);
           ans+=(e[i+1].h-e[i].h)*t[1].len;
       }
       printf("Test case #%d\nTotal explored area: %.2f\n\n",++q,ans);
       /* 
        这里注意下扫描线段时r-1:int R=search(s[i].l,pos,m)-1; 
        计算底边长时r+1:if(mark[n])sum[n]=pos[right+1]-pos[left]; 
        解释:假设现在有一个线段左端点是l=0,右端点是r=m-1 
        则我们去更新的时候,会算到sum[1]=pos[mid]-pos[left]+pos[right]-pos[mid+1] 
        这样的到的底边长sum是错误的,why?因为少算了mid~mid+1的距离,由于我们这利用了 
        离散化且区间表示线段,所以mid~mid+1之间是有长度的,比如hash[3]=1.2,pos[4]=5.6,mid=3 
        所以这里用r-1,r+1就很好理解了 
        */  
   }
    return 0;
}


参考博客:Stephen__

             

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值