掃瞄線及其應用

2021-10-12 16:23:05 字數 4261 閱讀 1287

原部落格位址

在乙個笛卡爾座標系內,用一根無限長線在此座標系內掃瞄,這根線就叫做掃瞄線,通俗易懂。

通常情況下,在座標系內確定一條線段需要兩個端點。但在特殊情況下,如該直線平行於 y

yy 軸,只需要三個資訊來確定:端點的縱座標,任意一點的橫座標。

即是:

struct scan_line 

scan_line

(int x,

int yd,

int yu,

int a)

#define x(x) line[x].coordx

#define yd(x) line[x].coordy_down

#define yu(x) line[x].coordy_up

};

掃瞄線比較特殊,一般情況下取用平行或垂直於 y

yy 軸的直線。

給一道例題來理解。

在笛卡爾座標系內,有 n

nn 個矩形,求這 n

nn 個矩形共同覆蓋的面積。

輸入的第一行乙個正整數 n

nn 。

接下來 n

nn 行每行四個非負整數 x1,

y1,x

2,y2

x_1, y_1, x_2, y_2

x1​,y1

​,x2

​,y2

​ ,表示乙個矩形的左下角座標為 (x1

,y1)

(x_1, y_1)

(x1​,y

1​) ,右上角座標為 (x2

,y2)

(x_2, y_2)

(x2​,y

2​) 。

輸出答案即可。

題目的意思用影象來表示:

那麼這 5

55 個矩形的面積並就為:

這些圖形都是很不規則的,但是把他們細分成以下幾個部分。

這樣劃分後,答案就為每乙個部分的低和高來求了。注意,高可能是斷斷續續的,如上圖的藍色部分,雖然是兩個矩陣,但是底相同。

那麼就可以使用掃瞄線從左到右掃瞄,計算每個部分的面積,每個部分的面積就為底乘高

大思路有了,現在來**怎麼實現。

不難發現,掃瞄線掃瞄到了矩形的邊的時候,長度發生改變。在具體一點,當掃瞄線掃瞄到矩形的左邊的邊時,長度可能增加,掃瞄到右邊的邊時,長度可能縮短。

進一步按照掃瞄線的橫座標進行排序,則掃瞄線的寬就出來了。按照這樣的掃瞄順序,就應該求出高為多少。

這又涉及到區間覆蓋的問題,掃瞄線究竟覆蓋了多少長度?前面說過,掃瞄線其實並不是連續的,有可能是斷斷續續的,而線段樹這種資料結構就成了首選。

線段樹主要維護的是這個區間內的線段覆蓋的情況,是一顆權值線段樹,由於資料較大,可以先使用類似離散化的思想來優化空間。

一共有 2n2n

2n條線段,因為有 n

nn 個矩形,而每個矩形都有兩條邊。每掃瞄到一條邊的時候,這條線段對應的區間就改變。設 c

cc 為該區間內有多少線段覆蓋,len

lenle

n 為線段的被覆蓋總長度,不難推出線段樹的子節點更新父節點的式子:

void

push_up

(int pos)

初始化建樹:

void

make_tree

(int pos,

int l,

int r)

若有區間被全覆蓋,則 len

lenle

n 就等於左右的距離,若沒有全覆蓋,就為兩個兒子的距離總和。可能會覺得多此一舉,但是若該區間已經被全覆蓋了,就不用遞迴到子節點了,可以省去很多時間,將 o(n

2)o(n^2)

o(n2

) 的演算法降到 o(n

log(

n))o(nlog(n))

o(nlog

(n))

,其實 c

cc 就相當於延遲標記,故而時間複雜度就可以模擬線段樹的 「區間修改 區間查詢」 問題。修改**如下:

void

update_tree

(int pos,

int l,

int r,

int k)

if(l

lc(pos)))

//與左兒子有交叉

update_tree(lc

(pos)

, l, r, k);if

(r >l(

rc(pos)))

//與右兒子有交叉

update_tree(rc

(pos)

, l, r, k)

;push_up

(pos)

;}

既然不遞迴到子節點,那麼程式會不會出錯?注意,我們想要查詢的是整個掃瞄線被覆蓋的長度,即是根節點被覆蓋的長度,不會去查詢子節點,所以不用擔心。

#include

#include

using

namespace std;

void

quick_read

(int

&n)while

(c >=

'0'&& c <=

'9')

n *= op;

}const

int maxn =

2e5+5;

struct segment_tree

#define lc(x) (x << 1)

#define rc(x) (x << 1 | 1)

#define l(x) tree[x].left_section

#define r(x) tree[x].right_section

#define c(x) tree[x].cover_section

#define len(x) tree[x].linear_length};

struct scan_line

scan_line

(int x,

int yd,

int yu,

int a)

#define x(x) line[x].coordx

#define yd(x) line[x].coordy_down

#define yu(x) line[x].coordy_up

#define a(x) line[x].addsub};

segment_tree tree[maxn <<3]

;scan_line line[maxn]

;int y[maxn]

;int n, now;

bool

cmp(scan_line x, scan_line y)

void

push_up

(int pos)

void

make_tree

(int pos,

int l,

int r)

void

update_tree

(int pos,

int l,

int r,

int k)

if(l

lc(pos)))

update_tree(lc

(pos)

, l, r, k);if

(r >l(

rc(pos)))

update_tree(rc

(pos)

, l, r, k)

;push_up

(pos)

;//子節點該邊父節點也有可能改變

}void

build()

void

scan()

printf

("%llu"

, res);}

void

read()

}int

main()

線段樹應用 掃瞄線

掃瞄線暴力解決的話時間和空間複雜度往往是不夠的。所以,掃瞄線也就成了線段樹很大的應用。具體原理解釋 寫得很好 很好 給出模板 第一篇部落格 饒齊大佬 的修正與改進,還有一些地方的解釋,錯誤原因 第乙個 二維平面有n個平行於座標軸的矩形,現在要求出這些矩形的總面積.重疊部分只能算一次.includeu...

P5490 模板 掃瞄線 掃瞄線

題目描述 求 n 個矩形的面積並。輸出格式 一行乙個正整數,表示 n 個矩形的並集覆蓋的總面積。発生 線段樹開小了,因為n變成了兩倍,線段樹就得開4 2 8倍 對每一根掃瞄線,維護所截得的長度,每次乘以兩根掃瞄線高度差就得到了面積並 截得長度用線段樹維護即可 注意線段樹需要離散化 include i...

掃瞄線入門

聽說掃瞄線很牛掰,於是就見識了一下。之前做過一道掃瞄線的題,brother,就是判斷矩形是否被矩形內部的車攻擊到。當時是把矩形拆成出邊和入邊 豎直的 把所有小於x2的車加進圖中,掃 y1,y2 中x最小的車的x.和x1判斷比較。然後交換x,y掃一遍 當時只是感覺線段樹很神奇,還可以這樣搞,後來才知道...