POJ1151Atlantis 矩形面積並 掃描線 線段樹
歡迎訪問~原文出處——博客園-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 矩形面積並 掃描線 線段樹