1224 . 矩形覆蓋面積計算
題意:給你平面上n個矩形,請求出它們覆蓋的總表面積。
這一題所使用的技巧是掃描線以及線段樹,下圖中的水平藍色線即為掃描線,由y=0開始往上掃描,當遇到了矩形的邊,利用線段樹查詢區間內當前的矩形寬度,乘上兩掃描線的高度差即為面積。當然,掃描線也可以使用垂直方向的線段由左而右的掃描,實作細節是一樣的。
線段樹維護
方法一
我們可以定義線段樹$seg[cur]$為區間$[l,r]$中有被矩形覆蓋的大小有多大,也就是圖中當前掃描線對應到的區域的寬度。這樣子維護有一個問題,當我們直接用$seg[cur]$儲存答案,我們在修改的時候沒有辦法確切知道這段區間被覆蓋的情況。
下圖為一種模擬的情況,每一個區間的數字代表著非0的數字個數,也就是它的寬度。今天我們要對區間$[4,6]$加減值,將區間拆成$[4,4]$跟$[5,6]$,這時候區間$[3,4]$的數值是1,我們卻不知道到底是3還是4是有被覆蓋到的,必須要遞迴下去到葉節點才能得到完整的覆蓋情況,這時候每一次加減值的複雜就會提升到$O(n)$,因此不能以這種方式維護。
方法二
有別於第一種方法對$seg[id]$進行維護,我們可以多開一個區間 $tag$ 來紀錄被矩形覆蓋的情況。下圖有3個矩形,其中的數字代表每一塊區域被覆蓋的情況,這邊使用了$tag$來紀錄(他是附在區間上的,不會像圖中一樣的方式呈現)。tag的數值為非負整數,紀錄當前區間有多少矩形覆蓋在上面,用$tag$來輔助維護$seg[id]$可以在$O(logn)$的時間進行修改與查詢。
以下程式碼是是 $tag$ 的轉移,當大的區間的tag值不為0,代表有一個矩形曾完整覆蓋這個區間,這時候可以直接回傳區間大小,否則即回傳左右節點的$sed[left],seg[right]$的數值。
這邊定義$seg[id]$為:「考慮 id 的子孫們(不含 id 本身)的所有 tag 值,假設這些子孫只有被tag值作用過,共有多少非0的數字」。
1 | seg[cur].val = (seg[2*cur].tag?mid-l:seg[2*cur].val) |
實作方法
矩形維護
首先是維護矩形的方法。我們一個矩形總共要維護四個東西:矩形左界x1、矩形右界x2、矩形上下界的y座標(分上下兩條),這兩條邊是下界或是上界val。為什麼要水平方向要分兩條討論?是因為下界代表進入,當掃描線掃到這一條邊的時候表示我們要新增區間 $[x1,x2)$ 進入線段樹;反之如果掃到了上界,則表示離開這個矩形,在線段樹中扣掉區間 $[x1,x2)$。
1 | struct Node{ //每一個矩陣分成上下兩條邊 |
上下界我們利用val維護,當 $val=1$ 時表示是矩形的下界; $val=-1$ 則是矩形上界,這兩個搭配在一起剛好就可以用線段樹區間加值的方式進行操作!總共有 $n$ 個矩形,因此我們要掃描線總共掃描 $2n$ 條線段。
線段樹
一樣對值域(這題是1000000)的4倍開了線段樹,同時維護一個非負整數 $tag$ 表示區間被覆蓋的情況。當每一次修改完成之後,我們可以直接取用根節點 $seg[1]$ 的數值表示寬度(非0的個數)!
1 | //seg[i]表示i的左右兩子樹的區間非0的個數 |
接下來就是在程式執行的過程中將 $2n$ 條邊依照y座標進行排序 $O(nlogn)$,接著依序使用掃描線搭配線段樹的修改,計算矩形的面積。最後就是輸出加起來的答案。
Debug 小錯誤
Submission1:WA
可以看到有一筆測資過不了,95分QQQ
後來debug之後發現到,因為我是對每一個矩形先輸入下界之後才是上界,當我在排序的過程中,上界有可能有機會跑到下界之前,造成 $tag$ 被扣到負的情況,但在定義中可以清楚知道 $tag$ 是非負整數造成錯誤。因此只要把排序的過程改成 stable_sort() 即可!
1 | stable_sort(arr,arr+(n<<1),cmp); |
最後終於是程式碼的部分,以下:
1 |
|