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;
}





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