HDU-1542-Atlantis (线段树 + 扫描线 + 离散化)

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. 
InputThe 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.OutputFor 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 


题意:给你n个矩形的左下角坐标和右上角坐标,求矩形相交的面积。

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

--------------------扫描线------------------

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

我们还要保存矩形的上下边的信息,并且如果是下边记录为1,如果是上边记录为-1

用4条横线将整个图划分成了5个部分,显然此时再算面积就可以用各个颜色的部分求和。

首先,我们看到了最下面灰色矩形的下边,

用这个下边的长度乘以这条边和上一条边的高度差即得到灰色矩形面积,

继续看到蓝色的矩形的下边,虽然蓝色矩形有两个,但我们计算时自然会用结合律将两个矩形的下边加起来再去乘以同样的高,

后重复这样的操作,我们最终可以求得整个图形的面积。

我们以整个图最左边的竖线作为区间左端点,最右边的竖线作为区间右端点建立线段树,去维护这个区间的有效长度(即被覆盖的长度

比如扫到第2条边的时候,有效长度就是两个蓝色矩形的宽之和。

这样,我们用扫描线去扫描每一条边的时候,都需要更新线段树的有效长度。

扫描线从下往上扫描,每遇到一条上下边就停下来,将这条线段投影到总区间上(总区间就是整个多边形横跨的长度),这个投影对应的其实是个插入和删除线段操作。,描到上边相当于在总区间删除一条线段(如果说插入删除比较抽象,那么就直白说,扫描到下边,投影到总区间,对应的那一段的值都要增1,扫描到上边对应的那一段的值都要减1,如果总区间某一段的值为0,说明其实没有线段覆盖到它,为正数则有,不会为负数)。

利用线段树把每条边的有效长度找到了,也就是找到了每部分的所有矩形的总宽,高就简单多了,对于所有的边,按照高度从小到大排列,那么矩形高就是每相邻边之间的高度差。

所谓的离散化,简单说就是将一组很大的数据,浓缩为一组很小的数据,用这组数据来代替原数据的作用。

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
#define lson l , m , rt << 1  
#define rson m + 1 , r , rt << 1 | 1  
#define mid (l+r)>>1  
#define abbreviations int l,int r,int rt  
const int N = 505;
int add[N<<2];                               //add为区间标记,与懒惰标记类似
double x[N<<2],sum[N<<2];                    //记录横坐标和区间覆盖总长度
struct node
{
    int f;                                   //f = 1为下边,f = -1为上边
    double l,r,h;                            //分别记录线段的左端,右端和高度
    bool operator<(const node b) const       //因为是从下到上扫描因此,需要按照高度排序
    {      
        return this->h < b.h;
    }
}s[N];
void pushup(abbreviations)
{
    //非零,已经被整段覆盖 
    if(add[rt])                               //这就是如何解决的线段重叠的办法,如果当前区间
        sum[rt] = x[r+1] - x[l];              //有标记用x数组更新,而不是左右儿子更新
    //这是一个点而不是线段
    else if(l == r)
        sum[rt] = 0;
    //是一条没有整个区间被覆盖的线段,合并左右子树的信息
    else
    sum[rt] = sum[rt<<1] + sum[rt<<1|1];         //与懒惰标记的区别在于标记不会消失(除非遇到上边)
}                                               //这也就是线段不会重复相加减的关键

void update(int L,int R,int c, abbreviations)     //这里深刻体会为什么令下边为1,上边-1
{                                                 //下边插入边(成段加一),上边删除边(成段减一)
    int m;
    if(L <= l && r <= R)
    {
        add[rt] += c;                          //update与普通线段树基本相同
        pushup(l, r, rt);                      //更新区间被覆盖de总长度
        return;
    }
    m = mid;
    if(L <= m)
        update(L,R,c,lson);
    if(R > m)
        update(L,R,c,rson);
    pushup(l, r, rt);
}
int main()
{                                        
    int n,i,l,m,r,cas;                 
    double a,b,c,d,ans;                 
    cas = 1;                             
    while(scanf("%d",&n)!=EOF&&n)        
    {                                    
        ans = m = 0;                      
        for(i = 0; i < n; i++)
        {
            scanf("%lf%lf%lf%lf",&a,&b,&c,&d); //输入一个矩形
            x[m] = a;
            s[m].l = a;
            s[m].r = c;
            s[m].h = b;
            s[m++].f = 1;
            x[m] = c;
            s[m].l = a;
            s[m].r = c;
            s[m].h = d;
            s[m++].f = -1;
        }
        sort(x,x+m);
        sort(s,s+m);
        int k = unique(x, x+m) - x - 1;        //对x坐标进行离散化,也可以用set
        memset(add,0,sizeof(add));
        memset(sum,0,sizeof(sum));
        for(i = 0; i < m-1; i++)
        {
             //因为线段树维护的是横坐标们的下标,所以对每条边求出其两个横坐标对应的下标
            l=lower_bound(x,x+k,s[i].l)-x; //在横坐标数组里找到这条边的位置
            r=lower_bound(x,x+k,s[i].r)-x-1;   //这是左闭右开的区间,因此r-1,普通线段树保存的是点,而扫描线保存的是线段所以r-1
            update(l,r,s[i].f,0,k-1,1);     //每扫到一条边就更新横向的覆盖sum
            ans += (sum[1]*(s[i+1].h-s[i].h));  //sum[1]是整个区间的有效长度
            //计算面积就是用区间横向的有效长度乘以两条边的高度差(面积是两条边里面的部分)
        }                                      
        printf("Test case #%d\nTotal explored area: %.2f\n\n", cas++, ans);
    }                                          
    return 0;                                  
}

猜你喜欢

转载自blog.csdn.net/sugarbliss/article/details/80568257
今日推荐