1. 程式人生 > >poj 1151 線段樹掃描線求圖形面積

poj 1151 線段樹掃描線求圖形面積

Atlantis
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 16975 Accepted: 6468

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

    掃描法就是用一根想象中的線掃過所有矩形,在寫程式碼的過程中,這根線很重要。方向的話,可以左右掃,也可以上下掃。方法是一樣的,這裡我用的是由下向上的掃描法。

     

如上圖所示,座標系內有兩個矩形。位置分別由左下角和右上角頂點的座標來給出。上下掃描法是對x軸建立線段樹,矩形與y平行的兩條邊是沒有用的,在這裡直接去掉。如下圖。

現想象有一條線從最下面的邊開始依次向上掃描。線段樹用來維護當前覆蓋在x軸上的線段的總長度,初始時總長度為0。用ret來儲存矩形面積總和,初始時為0。

由下往上掃描,掃描到矩形的底邊時將它插入線段樹,掃描到矩形的頂邊時將底邊從線段樹中刪除。而在程式碼中實現的方法就是,每條邊都有一個flag變數,底邊為1,頂邊為-1。

用cover陣列(通過線段樹維護)來表示某x軸座標區間內是否有邊覆蓋,初始時全部為0。插入或刪除操作直接讓cover[] += flag。當cover[] > 0 時,該區間一定有邊覆蓋。

開始掃描到第一條線,將它壓入線段樹,此時覆蓋在x軸上的線段的總長度L為10。計算一下它與下一條將被掃描到的邊的距離S(即兩條線段的縱座標之差,該例子裡此時為3)。

       則 ret += L * S. (例子裡增量為10*3=30)

結果如下圖

  

橙色區域表示已經計算出的面積。

掃描到第二條邊,將它壓入線段樹,計算出此時覆蓋在x軸上的邊的總長度。

例子裡此時L=15。與下一條將被掃描到的邊的距離S=2。 ret += 30。 如下圖所示。

綠色區域為第二次面積的增量。

接下來掃描到了下方矩形的頂邊,從線段樹中刪除該矩形的底邊,並計算接下來面積的增量。如下圖。

 

藍色區域為面積的增量。

此時矩形覆蓋的總面積已經計算完成。 可以看到,當共有n條底邊和頂邊時,只需要從下往上掃描n-1條邊即可計算出總面積。

============ =================  分割線      ==============================

此題因為橫座標包含浮點數,因此先離散化。另外,因為用線段樹維護的是覆蓋在x軸上的邊,而邊是連續的,並非是一個個斷點,因此線段樹的每一個葉子結點實際儲存的是該點與下一點之間的距離。

幾個圖說明掃描線掃描..線段樹維護的過程..:


初始狀態


掃到最下邊的線,點更新1~3為1


掃到第二根線,此時將計數器不為0的長度*上線兩根線的長度,得到綠色的面積,加到答案中去.隨後更新計數


同上,將黃色的面積加到答案中去


同上,將灰色的面積加到答案中去


同上,將紫色的面積加到答案中去


同上,將藍色的面積加到答案中去

#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
#define N 1000
#define eps 1e-6
using namespace std;
double x[N];
struct line
{
    double l,r,h;
    int flag;
    line(){};
    line(double ll,double rr,double hh,int fflag)
    {
        l=ll; r=rr; h=hh;  flag=fflag;
    }
}bian[N];
struct node
{
    int x,y,p;
    double sum;
}a[N*3];
bool cmp(line a,line b)
{
    return a.h<b.h;
}
int binary(double k,int l,int r)
{
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(fabs(x[mid]-k)<=eps)
            return mid;
        else if(x[mid]>k)
            r=mid-1;
        else
            l=mid+1;
    }
    return l;
}
void build(int t,int x,int y)
{
    a[t].x=x;
    a[t].y=y;
    a[t].sum=0;
    a[t].p=0;
    if(x==y)  return;
    int mid=(x+y)>>1,temp=t<<1;
    build(temp,x,mid);
    build(temp+1,mid+1,y);
}
void pushdown(int t)
{
    int temp=t<<1;
    if(a[t].p)//如果當前區間存在,不用看兒子的,直接計算區間和
       a[t].sum=x[a[t].y]-x[a[t].x-1];
    else if(a[t].x==a[t].y)//如果區間沒有兒子了,而且不存在了,區間和為0
        a[t].sum=0;
    else//如果當前區間不存在了,但是有兒子,它的區間和由兒子決定
       a[t].sum=a[temp].sum+a[temp+1].sum;
}
void update(int xx,int yy,int c,int t)
{
    if(a[t].x==xx&&a[t].y==yy)
    {
        a[t].p+=c;
        pushdown(t);
        return;
    }
    int mid=(a[t].x+a[t].y)>>1,temp=t<<1;
    if(yy<=mid)
        update(xx,yy,c,temp);
    else if(xx>mid)
        update(xx,yy,c,temp+1);
    else
    {
        update(xx,mid,c,temp);
        update(mid+1,yy,c,temp+1);
    }
    pushdown(t);
}
int main()
{
    int t,cnt=1,i,k,j,xx,yy,n;
    double x1,y1,x2,y2,ans;
    while(scanf("%d",&n),n!=0)
    {
        k=0;
        for(i=0;i<n;i++)
        {
           scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
           bian[k]=line(x1,x2,y1,1);
           x[k++]=x1;
           bian[k]=line(x1,x2,y2,-1);
           x[k++]=x2;
        }
        sort(bian,bian+k,cmp);
        sort(x,x+k); j=1;
        for(i=1;i<k;i++)//去重點
        {
            if(x[i]!=x[i-1])
                x[j++]=x[i];
        }
        ans=0;
        build(1,1,j-1);
        for(i=0;i<k-1;i++)
        {
            xx=binary(bian[i].l,0,j-1)+1;
            yy=binary(bian[i].r,0,j-1);
            //printf("%d %d %d\n",xx,yy,bian[i].flag);
            if(xx<=yy)
                update(xx,yy,bian[i].flag,1);
            //printf("%lf\n",a[1].sum);
            ans+=(bian[i+1].h-bian[i].h)*a[1].sum;
        }
        printf("Test case #%d\n",cnt++);
        printf("Total explored area: %.2lf\n\n",ans);
    }
    return 0;
}