原部落格位址
在乙個笛卡爾座標系內,用一根無限長線在此座標系內掃瞄,這根線就叫做掃瞄線,通俗易懂。
通常情況下,在座標系內確定一條線段需要兩個端點。但在特殊情況下,如該直線平行於 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掃一遍 當時只是感覺線段樹很神奇,還可以這樣搞,後來才知道...