poj 1151 Atlantis “线段树维护关键值”+“离散化”+“扫描线法”

Atlantis
Time Limit: 1000MS   Memory Limit: 10000K
Total Submissions: 17448   Accepted: 6652

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 

Source

[Submit]   [Go Back]   [Status]   [Discuss]


题目大意:

n个矩形,给出所有矩形的左上顶点座标和右下顶点座标,求解举行面积并。


解题思路:

第一次接触求解矩形面积并的题目,首先找了网上的代码和方法学习了一下。

这里使用的是用“线段树维护关键值”+“离散化”+“扫描线法”的方法。


(1)首先,因为横纵座标的范围比较大,不能直接用横纵座标来建立线段树。

所以可以用一个y数组来记录所有的纵座标,然后排序去除重复元素。再用这个数组的下标来建立线段树

这样,在用到线段树求取矩形纵边长的时候就可以用线段树节点的左右端点作为索引求解实际的纵座标了。


(2)接下来说一下线段树的作用,这里主要是维护一个len值

线段树的节点有两个域:

一个是cover用来表示之前是否有能够与当前边组成矩形的边。当遇到一条左边时,就cover++,遇到一条右边时就cover--。

这样每当扫描线经过一个完整的矩形后就相当于cover的值没有改变了。

另一个域是len用来表示加入当前边之后可用来求解面积并的边长值。这么一来,扫描时,每加入一条边只需要ans加上len*(上一条边的x-当前边的x)即可。

而这个len值在每次加入边的时候都更新到根节点,tree[1]上,直接使用tree[1].len即可。


注意:

这里采用的是“与y轴平行的扫描线”,那么需要离散y座标。如果采用的是“与x轴平行的扫描线”,则离散横座标。

自己还是讲不很清楚,如果有疑惑可以再到这来看看,里面还有另外一种实现:

http://www.cnblogs.com/ka200812/archive/2011/11/13/2247064.html



下面是我的ac代码:

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <memory.h>
#include <string>
#include <vector>
#include <list>
#include <map>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <numeric>
#include <functional>
#define maxn 500

using namespace std;
int n,len;
struct node
{
    int l;
    int r;
    int cover;
    double len;
};
node tree[5*maxn];
struct LINE{
    double x;
    double y_up,y_down;
    int l_or_r;
};
LINE line[maxn];
double y[maxn];

int findindex(double yy)          //二分查找
{
    int l=0,r=len;
    while(l<=r){
        int mid=(l+r)/2;
        if(yy==y[mid]){
            return mid;
        }
        else if(yy>y[mid]){
            l=mid+1;
        }
        else{
            r=mid-1;
        }
    }
    return l;
}

int comp(const LINE &a,const LINE &b)
{
    return a.x<b.x;
}

void build(int i,int l,int r)
{
    tree[i].l=l;
    tree[i].r=r;
    tree[i].len=0;
    tree[i].cover=0;

    if(l+1==r){
        return ;
    }
    int mid=(l+r)/2;
    build(2*i,l,mid);
    build(2*i+1,mid,r);
}



void update(int i,int l,int r,int cover)
{
    if(tree[i].l>r || tree[i].r<l)
        return;
    if(tree[i].l>=l && tree[i].r<=r)
    {
        tree[i].cover+=cover;
        if(tree[i].cover)
        //如果cover大于1,那么整段都可用于与下一线段求并面积
            tree[i].len=y[tree[i].r]-y[tree[i].l];
        else if(tree[i].l+1==tree[i].r)
        //如果cover等于零,又到了叶子线段,那么可用线段长度就为零了
            tree[i].len=0;
        else
        //如果cover等于零,那么以为着,之前没有可以和当前边组成矩形的边,且没到叶子节点,直接计算一下len
            tree[i].len=tree[2*i].len+tree[2*i+1].len;
        return;
    }
    update(2*i,l,r,cover);
    update(2*i+1,l,r,cover);
    //因为先处理完当前节点的子孙节点,才执行下面的判断更新语句,所以最后可以用于求面积并的len存储在了根节点

    if(tree[i].cover)
    //如果cover大于1,那么整段都可用于求并面积
        tree[i].len=y[tree[i].r]-y[tree[i].l];
    else if(tree[i].l+1==tree[i].r)
    //如果cover等于零,又到了叶子线段,那么可用线段长度就为零了
        tree[i].len=0;
    else
    //如果cover等于零,那么以为着,之前没有可以和当前边组成矩形的边,且没到叶子节点,直接计算一下len
        tree[i].len=tree[2*i].len+tree[2*i+1].len;
}

int main()
{
    int cas=1;
    while(1){
        scanf("%d",&n);
        if(!n) break;

        int m=0;
        for(int i=0;i<n;i+=1){
            double a1,b1,a2,b2;
            scanf("%lf %lf %lf %lf",&a1,&b1,&a2,&b2);

            y[m]=b1;           //y存储所有的y座标
            line[m].x=a1;
            line[m].y_up=b1;
            line[m].y_down=b2;
            line[m++].l_or_r=1;

            y[m]=b2;     //y存储所有的y座标
            line[m].x=a2;
            line[m].y_up=b1;
            line[m].y_down=b2;
            line[m++].l_or_r=-1;
        }
        sort(y,y+m);
        len=1;
        for(int i=1;i<m;i+=1){    //离散化,并且去除重复的纵座标
            if(y[i-1]!=y[i]){
                y[len++]=y[i];
            }
        }
        len--;
        build(1,0,len);
        sort(line,line+m,comp);
        double ans=0;
        printf("Test case #%d\n",cas++);
        for(int i=0;i<m-1;i+=1){
            int a=findindex(line[i].y_down);     //用line的y值二分出对应的y数组的下标
            int b=findindex(line[i].y_up);
            update(1,b,a,line[i].l_or_r);
            ans+=tree[1].len*(line[i+1].x-line[i].x);  //tree[1].len已经保留了整个树与line[i+1]所能求并面积的长度
        }
        printf("Total explored area: %.2lf\n\n",ans);
    }

    return 0;
}





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章