1. 程式人生 > >POJ1151Atlantis 矩形面積並 掃描線 線段樹

POJ1151Atlantis 矩形面積並 掃描線 線段樹

大小 class 時間 cnblogs 優化 進入 while poj 空間

歡迎訪問~原文出處——博客園-zhouzhendong

去博客園看該題解


題目傳送門 - POJ1151


題意概括

  給出n個矩形,求他們的面積並。

  n<=100


題解

  數據範圍極小。

  我們分3種算法逐步優化。

  算法1: O(n3)

  如果這n個矩形的坐標都是整數,而且比較小,那麽我們顯然可以用最暴力的方法:一個一個打標記。

  但是不是這樣的。

  坐標大小很大,而且是實數。

  然而我們發現差不多,只要先離散化一下,然後再打標記即可。

  算法2:O(n2)

  實際上,上面的方法十分慢。如果n的範圍到了1000,上面的就無濟於事了。

  而實際上,基於上面的打標記的算法,我們可以通過差分的方法n2

解決。

  我們通過差分,可以用n2的時間標記,n2的時間判斷每一個區域是否被覆蓋。

  空間復雜度O(n2)

  算法3:O(n logn) 掃描線

  實際上,這類問題的數據範圍可以到100000這個級別。

  矩形面積並可以用掃描線算法來解決。先看原理,後面講具體實現。

  比如下圖:

  技術分享

  當前我們的掃描線到達了淡黃色部分。

  由於之前沒有記錄,所以答案不增加。

  然而我們記下當前橫向覆蓋的長度。

技術分享

  然後我們到了第二條掃描線,加上原來記錄的橫向覆蓋長度乘以增加的高度就是當前增加的答案。

  然後,我們更新了橫向覆蓋的長度。

  繼續。

  技術分享

  然後第三條。現在的橫向覆蓋長度是兩邊加起來,所以增加的面積是兩塊了。

  然後更新橫向覆蓋的長度,加上了中間的那一條。

  然後繼續。

  技術分享

  現在有這麽長的一條都是被橫向覆蓋的了。

  所以新增的面積是淺藍色部分。

  然後我們發現左上那條線是出邊,所以要刪除這一條線。

  所以橫向覆蓋的長度為如下:

  技術分享

  同理,接下來是:

  技術分享

  然後就OK了。

  

  那麽具體怎麽實現呢?

  我們開一棵線段樹來維護!

  在讀入之後,我們把所有的橫線都拆開,分成下邊和上邊兩類。某一區間在進入下邊的時候+1,離開上邊的時候-1,所以我們分別給上下邊標記+1和-1。

  對於Y,我們離散化一下。

  對於X,我們按照邊的X排一個序。

  然後按照剛才那樣的處理。

  具體如何維護詳見代碼。


代碼

#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
using namespace std;
const int N=100+5,M=N*2;
const double Eps=1e-9;
int T=0,n,m,tot_Y,tot_s;
double Y[M];
struct Segment{
    double x,L,R;
    int v;
    void set(double x_,double L_,double R_,int v_){
        x=x_,L=L_,R=R_,v=v_;
    }
}s[M];
struct SegTree{
    int cnt;
    double sum;
}t[M*4];
bool cmp_s(Segment a,Segment b){
    return a.x<b.x;
}
void build(int rt,int le,int ri){
    t[rt].cnt=0;
    t[rt].sum=0;
    if (le==ri)
        return;
    int mid=(le+ri)>>1,ls=rt<<1,rs=ls|1;
    build(ls,le,mid);
    build(rs,mid+1,ri);
}
void pushup(int rt,int le,int ri){
    int ls=rt<<1,rs=ls|1;
    if (t[rt].cnt)
        t[rt].sum=Y[ri+1]-Y[le];
    else if (le==ri)
        t[rt].sum=0;
    else
        t[rt].sum=t[ls].sum+t[rs].sum;
}
void update(int rt,int le,int ri,int xle,int xri,int d){
    if (le>xri||ri<xle)
        return;
    if (xle<=le&&ri<=xri){
        t[rt].cnt+=d;
        pushup(rt,le,ri);
        return;
    }
    int mid=(le+ri)>>1,ls=rt<<1,rs=ls|1;
    update(ls,le,mid,xle,xri,d);
    update(rs,mid+1,ri,xle,xri,d);
    pushup(rt,le,ri);
}
int find_double(double x){
    int le=1,ri=m,mid;
    while (le<=ri){
        mid=(le+ri)>>1;
        if (abs(x-Y[mid])<Eps)
            return mid;
        if (Y[mid]<x)
            le=mid+1;
        else
            ri=mid-1;
    }
}
int main(){
    while (scanf("%d",&n)&&n){
        tot_Y=tot_s=0;
        for (int i=1;i<=n;i++){
            double xA,yA,xB,yB;
            scanf("%lf%lf%lf%lf",&xA,&yA,&xB,&yB);
            if (yB-yA<Eps||xB-xA<Eps)
                continue;
            Y[++tot_Y]=yA,Y[++tot_Y]=yB;
            s[++tot_s].set(xA,yA,yB,1);
            s[++tot_s].set(xB,yA,yB,-1);
        }
        sort(Y+1,Y+tot_Y+1);
        sort(s+1,s+tot_s+1,cmp_s);
        m=1;
        for (int i=2;i<=tot_Y;i++)
            if (Y[i]-Y[i-1]>Eps)
                Y[++m]=Y[i];
        build(1,1,m);
        double ans=0;
        for (int i=1;i<=tot_s;i++){
            ans=ans+(s[i].x-s[i-1].x)*t[1].sum;
            int L=find_double(s[i].L);
            int R=find_double(s[i].R);
            update(1,1,m,L,R-1,s[i].v);
        }
        printf("Test case #%d\nTotal explored area: %.2lf\n\n",++T,ans);
    }
    return 0;
}

POJ1151Atlantis 矩形面積並 掃描線 線段樹