1. 程式人生 > >[翻譯]掃描線演算法(Line Sweep Algorithm)(2)

[翻譯]掃描線演算法(Line Sweep Algorithm)(2)

NOIPD110分滾粗。
心累。
學點有趣的治癒一下。
突然想起似乎之前還有個坑沒有填,就練一波英語閱讀。

矩形面積交

給出一個集合包含N個與座標軸對稱的矩形(矩形的邊與x軸、y軸平行),找到所有的矩形的重疊部分。其中一個矩形由兩個點代表,一個是左下角的點,一個是右上角的點。
這個問題的事件,是垂直的邊。當我們遇到一條左邊,我們進行一些操作;遇到一條右邊,進行另一些操作。左邊由左下角來代表,右邊由右上角代表。
我們以對x座標的排序來開始整個演算法。當一個長方形左下角的點被遇到(也就是說我們遇到了長方形的左邊),我們將之插入到集合中。當我們遇到了右上角的點(也就是遇到了長方形的右邊),我們將長方形從集合中清除出去。在任何情況,集合中只存在被掃描線掃到的矩形(即已遇到左邊但未遇到右邊)。
掃到的面積是Δy*Δx,其中Δy是掃描線被矩形切割的長度(下圖中的紅色部分),Δx為兩個掃描線事件之間的距離。
但是現在我們只知道哪些是被掃描線被切割的矩形。因此,這裡我們有一個新的問題:如何找到被切割的最大長度?
其做法與我們剛才(參見上一篇)做的類似。我們仍然用掃描線技術,不過這條線現在要旋轉90°,換言之,我們從下到上去掃描被掃到的矩形。我們用一個變數cnt維護當前重疊的矩形數量。當我們遇到一條底邊就讓cnt+1,遇到一條上邊就讓cnt-1,當cnt從非0變為0時我們就找到了掃描線被切割的長度。
下面來看看它是如何執行的。







上面的影象向我們展示了水平掃描線的執行過程, Δy 就是最後一張圖片展示的兩個箭頭長度之和。
這就是我們的演算法,接下來考慮列舉的部分。對於每一個掃描線的事件,我們需要找到掃描線切出的長度,也就是推進水平掃描線長度。這裡以bool陣列作為資料結構,因為我們可以分別以橫、縱來做一次排序。
下面是cpp程式碼:

#define MAX 1000
struct event 
{
    int ind; // 眾多矩形中該矩形的索引
    bool type; // 事件型別,0表示左下,1表示右上
    event() {};
    event(int ind, int
type) : ind(ind), type(type) {}; }; struct point { int x, y; }; point rects [MAX][12]; // 每個矩形包含兩個頂點: [0]=左下;[1]=右上 bool compare_x(event a, event b) { return rects[a.ind][a.type].x<rects[b.ind][b.type].x; } bool compare_y(event a, event b) { return rects[a.ind][a.type].y<rects[b.ind][b.type
].y; } int union_area(event events_v[],event events_h[],int n,int e) { //n是矩形數量, e=2*n , e是頂點數量(在矩形集合中每個矩形以兩個點被申明) bool in_set[MAX]={0}; int area=0; sort(events_v, events_v+e, compare_x); //豎直邊的預處理 sort(events_h, events_h+e, compare_y); // 水平邊的預處理 in_set[events_v[0].ind] = 1; for (int i=1;i<e;++i) { // 水平掃描線 event c = events_v[i]; int cnt = 0; // 表明有多少矩形重疊的計數器 // Delta_x: 當前線與之前線的距離 int delta_x = rects[c.ind][c.type].x - rects[events_v[i-1].ind][events_v[i-1].type].x; int begin_y; if (delta_x==0){ in_set[c.ind] = (c.type==0); continue; } for (int j=0;j<e;++j) if (in_set[events_h[j].ind]==1) //當前矩形的水平掃描線 { if (events_h[j].type==0) //是底邊 { if (cnt==0) begin_y = rects[events_h[j].ind][0].y; // 某一塊開始 ++cnt; //重疊矩形增加量 } else //是上面的線 { --cnt; //矩形已經不被重疊,所以移除之 if (cnt==0) //一塊的結束 { int delta_y = (rects[events_h[j].ind][13].y-begin_y);//豎直掃描線被切割的長度 area+=delta_x * delta_y; } } } in_set[c.ind] = (c.type==0);//如果是左邊, 矩形在當前集合中,否則不在 } return area; }

複雜度顯然是O(n^2)。如果用其它資料結構(如BST)維護,可以降到O(nlogn)
現在,你已經多少了解這項技術了,不是嗎?(並不)
讓我們去下一個可以用這項技術解決的問題吧。(沒錯,就是凸包)