線段樹之掃描線(HDU-1542)

線段樹之掃描線(HDU-1542)

掃描線也算是線段樹的經典應用了,本人也是因爲掃描線而接觸到線段樹的,當時做題的時候發現掃描線都是處理一條條的線段,然後搭配上線段樹,讓我以爲線段樹的線段就是這麼來的(其實我現在也不知道線段樹爲什麼叫做線段樹)。

一、常見應用題型

個人認爲掃描線的做法主要是用在處理矩形並或者立體交的情況下,矩形並可以求的是多個矩形任意重疊後整個圖形的面積 or 重疊了兩次(多次)的區域面積 or 整個圖形的周長 ……,立體交常見的是求多個長方體任意重疊後整個立體的體積 ……

如果使用普通其他的做法效率低下,使用線段樹的話性能就十分優秀了!

二、基本思路

水平有限,這裏只討論平面矩陣交的情況:

在這裏插入圖片描述

如圖所示的兩個矩陣重疊在一起,假設我們現在需要求這個圖形的總面積,不要想着求出三個矩陣的面積,然後兩個大的減去重疊的矩陣面積,這種做法在兩個矩陣的時候尚且可以接受,但是一旦數量變大,這樣就極其麻煩。

換個想法,這個圖形的面積可以由這三個部分組成:

在這裏插入圖片描述

這樣子看的話,我們只需要從下面往上將三個矩形的面積相加就可以了!

那麼如何讓計算機替我們完成這樣一件事呢,那就是線段樹+掃描線啦!

在這裏插入圖片描述

我們需要保存這①-④四條線段的左右端點以及高度,即 left、right 以及 h 三個參數。

從下往上掃描,遇到線段①,記錄線段的長度 len = x3 - x1,

繼續,遇到線段②,面積 S += len * (y2-y1),記錄線段的長度 len = x4 - x1,

遇到線段③,面積 S += len * (y3-y2),長度 len = x4 - x2,

遇到線段④,面積 S += len * (y4-y3),長度 len = 0,

結束,面積 S 就是矩形並的面積。

下面從實現的角度開始分析:

首先我們需要定義線段的結構保存每條線,

struct Edge {
    double left, right;
    double h;
    int flag;
    bool operator < (Edge other) const {
        return h < other.h;
    }
}edges[2*maxn];

爲了正確計算當前總共線段的長度 len,因此需要將線段分成入邊以及出邊,用flag來區分。

而重載 < 比較字符,是爲了後續的 sort 排序使用。

接下來我們還需要將線段涉及到的 x 座標進行離散化並且記錄,如果不清楚離散化的話,可以參考我博客中線段樹專題的 “線段樹之離散化”。

然後我們對所有的線段以及離散過後的 x 座標數組進行 sort 排序並 unique 去重。

這樣前期工作就做完了,我們就可以對開始建樹了,先是結點的結構:

struct Node {
    int left, int right;
    int cnt;
    double len;
}tree[4*maxn]

其中 cnt 用來保存區間 [left, right] 這條線段被覆蓋的次數,在掃描遇到入邊的時候,則 cnt++,遇到出邊則 cnt–,因爲總是先掃描到入邊再掃描到出邊,因此可以保證 cnt 總是 >= 0 的;

len 用來保存區間 [left, right] 這條線段中需要被記錄的線段長度,故當前總共線段的長度 = tree[0].len,很方便,當掃描到第 i 條 (i > 0) 線段的時候,面積 S += len * (edges[i]-edges[i-1]);

三、例題

Atlantis HDU - 1542

這道題目常常被用來當做矩陣並的模板。

題目大意:給定 n 個任意重疊的矩形地圖(但地圖的橫邊豎邊分別平行於 x 軸與 y 軸),求重疊後整體的面積。

input:多組輸入,第一行爲 n,若 n = 0 則不處理結束程序,接下來 n 行每行都包含矩形地圖的左上角座標與右下角座標。

output:按照 sample output 這樣的形式輸出整體的總面積,每組輸入後要接一行空行。

sample input:

2
10 10 20 20
15 15 25 25.5
0

sample output:

Test case #1
Total explored area: 180.00 

線段樹掃描線的裸題,正是要求矩形並的面積,注意數據是 double 類型的,這樣就更加表明了對數據進行離散化的必要性,還要注意每組輸出之後都要接一行空行。

參考代碼:

// 線段樹 掃描線 模板
#include<stdio.h>
#include<string>
#include<algorithm>
#include<iostream>
#define LL long long
using namespace std;

const int maxn =210;
LL N;
double x[4*maxn];

struct Edge {
	double left, right;
	double h;
	int flag;  // 判斷是入邊還是出邊
	bool operator < (Edge other) const {
		return h < other.h;
	}
} edges[4*maxn];

struct Node {
	LL left, right;
	LL cnt;
	double len;
} tree[4*maxn];

// 二分查找 val 離散化後的值 
LL findPos(LL l, LL r, double val) {
	LL mid;
	while(l <= r) {
		mid = (l+r)/2;
		if(x[mid] > val) r = mid-1;
		else if(x[mid] < val) l = mid+1;
		else break;
	}
	return mid;
}

void build(LL k, LL l, LL r) {
	tree[k].left = l;
	tree[k].right = r;
	tree[k].len = 0;
	tree[k].cnt = 0;
	if(l == r) return ;
	LL mid = (l+r)/2;
	build(2*k, l, mid);
	build(2*k+1, mid+1, r);
}

void pushUp(LL k) {
	if(tree[k].cnt)//非0,整段覆蓋
		tree[k].len = x[tree[k].right+1]-x[tree[k].left];
	else if(tree[k].left == tree[k].right)//葉子
		tree[k].len = 0;
	else//部分覆蓋
		tree[k].len = tree[2*k].len + tree[2*k+1].len;
}

void update(LL k, LL l, LL r, LL val) {
	if(l <= tree[k].left && tree[k].right <= r) { //全部包含
		tree[k].cnt += val;
		pushUp(k);
		return ;
	}
	LL mid = (tree[k].left + tree[k].right)/2;
	if(l <= mid)
		update(2*k, l, r, val);
	if(r > mid)
		update(2*k+1, l, r, val);
	pushUp(k);//計算該區間被覆蓋的總長度
}

int main() {
	LL K = 0;
	LL l, r;
	double x1, x2, y1, y2;
	while(~scanf("%d", &N), N) {
		LL cnt = 0;
		for(LL i = 1; i <= N; i++) {
			scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
			x[++cnt] = x1;
			edges[cnt].left = x1;
			edges[cnt].right = x2;
			edges[cnt].h = y1;
			edges[cnt].flag = 1;//下邊
			x[++cnt] = x2;
			edges[cnt].left = x1;
			edges[cnt].right = x2;
			edges[cnt].h = y2;
			edges[cnt].flag = -1;//上邊
		}
		sort(x+1, x+cnt+1);//排序
		sort(edges+1, edges+cnt+1);
		// 這裏沒有去重操作 可以加上 unique
		build(1, 1, cnt);
		double ans = 0;
		for(LL i = 1; i <= cnt; i++) { //拿出每條橫線並且更新
			l = findPos(1, cnt, edges[i].left);
			r = findPos(1, cnt, edges[i].right)-1;
			update(1, l, r, edges[i].flag);
			ans += tree[1].len*(edges[i+1].h-edges[i].h);//求面積
		}
		printf("Test case #%d\nTotal explored area: %.2f\n\n", ++K, ans);
	}
	return 0;
}

【END】感謝觀看!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章