『線段樹及掃描線算法 Atlantis』


<更新提示>

入門看這邊『線段樹 Segment Tree』

<第一次更新>


<正文>

掃描線

掃描線是一種解決一類平面內統計問題的算法,通常會借助線段樹來實現,我們通過一道例題來引入這個算法。

Atlantis

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 Format

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 Format

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

解析

題目大意:給定平面內的\(n\)個矩形,求這\(n\)個矩形的面積並。

這是掃描線算法的引入問題,我們嘗試設想有一條無限高的豎線左往右掃過這個並集圖形,按照每一個矩形的的左右邊界,我們可以將這個並集圖形分為\(2n\)段,對於兩兩相鄰的部分,我們可以分別計算面積,這樣就得到了整個並集圖形的面積。

如圖,我們就是把每個矩形的左右邊界提了出來,就變成了這樣一些線段。

那么我們需要這些量化記錄下來:每個四元組\((x,y_1,y_2,1/-1)\)分別代表了一條線段,\(x\)是線段的橫坐標,\((y_1,y_2)\)是線段上下端點的縱坐標,\(1/-1\)代表了這條線段是矩形的左邊界還是右邊界。

顯然,我們只需要把這些線段按照橫坐標排序,對於一次遍歷來說,兩兩線段之間的距離是已知的。那么我們需要解決的問題就是縱坐標的影響范圍。

我們不妨把縱坐標都取出來,離散化映射到\([1,T]\)之間的\(T\)個整數值,並將這些縱坐標表示為\(T-1\)段,其中第\(i\)段代表了第\(i\)個縱坐標和第\(i+1\)個縱坐標之間的部分,然后,我們設立數組\(c_i\)代表第\(i\)段被覆蓋的次數。

這樣,我們就可以用如下的算法流程計算矩形的面積:

\(1.\) 對於每一個線段,將其的\(k\)值累加到這個線段對應的若干個縱坐標區間

\(2.\) 計算面積:所有\(T-1\)個縱坐標區間對應的\(c\)值大於零的就說明這些部分的區間還存在,將存在的區間的長度累加起來,乘上當前線段與下一條線段之間的橫坐標之差就是這兩條線段之間的面積。

顯然,這里需要我們維護一個區間內的區間加法,區間求和,這個就是線段樹的事情了。

由於本題中的區間修改成對出現,互相抵消,所以我們可以不寫帶有\(lazytag\)的線段樹。我們在線段樹的每一個節點上維護兩個值\(cnt\)\(len\)\(cnt\)代表這段區間被覆蓋的次數,如果\(cnt>0\)則當前區間的\(len\)等於當前區間的縱坐標長度,反之\(len_p=len_{p*2}+len_{p*2+1}\)。那么對於每一次區間修改,我們直接在線段樹上改\(cnt\)的值即可,並沿路更新\(len\)值即可。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
const int N=120;
struct line
{
    double x,d,u;int flag;
}a[N<<1];
struct node
{
    int l,r,cnt;
    double len;
}v[N<<3];
int n,t,val[N<<1][2],T;
double raw[N<<2],ans;
inline bool compare(line p1,line p2){return p1.x<p2.x;};
inline void input(void)
{
    for (int i=1;i<=n;i++)
    {
        double x1,y1,x2,y2;
        scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
        a[2*i-1] = (line){x1,y1,y2,1};
        a[2*i] = (line){x2,y1,y2,-1};
        raw[++t] = y1 , raw[++t] = y2; 
    }
    sort(a+1,a+2*n+1,compare);
}
inline void discrete(void)
{
    sort(raw+1,raw+t+1);
    t = unique(raw+1,raw+t+1) - (raw+1);
    for (int i=1;i<=2*n;i++)
    {
        val[i][0] = lower_bound(raw+1,raw+t+1,a[i].d) - raw;
        val[i][1] = lower_bound(raw+1,raw+t+1,a[i].u) - raw;
    }
}
inline void updata(int p)
{
    if (v[p].cnt>0) v[p].len = raw[v[p].r+1] - raw[v[p].l];
    else if (v[p].l==v[p].r) v[p].len = 0;
    else v[p].len = v[ p<<1 ].len + v[ p<<1|1 ].len;
}
inline void build(int p,int l,int r)
{
    v[p].l = l , v[p].r = r;
    if (l==r){v[l].cnt = v[l].len = 0; return;}
    int mid = l+r >> 1;
    build( p<<1 , l , mid );
    build( p<<1|1 , mid+1 , r );
}
inline void modify(int p,int l,int r,int delta)
{
    if (l<=v[p].l&&r>=v[p].r)
    {
        v[p].cnt += delta;
        updata(p); return;
    }
    int mid = v[p].l+v[p].r >> 1;
    if (l<=mid) modify( p<<1 , l , r , delta );
    if (r>mid) modify( p<<1|1 , l , r , delta );
    updata(p);
}
inline double query(void){return v[1].len;}
inline void solve(void)
{
    for (int i=1;i<=2*n;i++)
    {
        modify(1,val[i][0],val[i][1]-1,a[i].flag);
        ans += (a[i+1].x-a[i].x) * query();
    }
}
int main(void)
{
    while ( scanf("%d",&n) && n )
    {
        ans = t = 0;
        input();
        discrete();
        build(1,1,t);
        solve();
        printf("Test case #%d\nTotal explored area: %.2lf\n\n",++T,ans);
    }
    return 0;
}

<后記>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM