線段樹 掃瞄線 P5490 模板 掃瞄線

2022-06-03 08:24:07 字數 3086 閱讀 4657

首先有這麼一張圖,要求它的面積並。

我們想,如果可以有一條掃瞄線從下往上掃,記錄它所掃到的邊的長度(並)\(len\),再求出這條邊與即將掃到的下一條邊的距離\(h\),那麼我們就可以求出第一塊面積(紫色)\(s_1=len\times h\)

然而如何求出這個\(len\)?

顯然只用當前邊的長度是不行的,如:當掃到邊\(r_2\)的時候,\(len\)應該是\(250-100=150\)這麼長

什麼情況下應該把之前掃過的邊的長度也考慮進去?

我們注意到,當乙個矩形只有下邊被掃過的時候,對這個矩形的面積計算仍未結束,那麼這個矩形的下邊長度應該被算進\(len\)中。當這個矩形的上邊被掃過之後,它的下邊長度不需要再考慮到\(len\)中了。

因此我們需要對矩形的上下邊進行區分:將下邊標記為1,上邊標記為-1。

當掃到邊\(r_2\)時,\(r_2\)與\(r_1\)有重疊部分。新的問題又來了:如何不重不漏地計算這個\(len\)?

我們將所有矩形的端點投影到x軸上,結果如下圖:

當掃到邊\(r_1\)時,區間[x1,x2]與[x2,x3]被覆蓋了。我們記錄下區間的被覆蓋次數。

當掃到邊\(r_2\)時,區間[x2,x3]與[x3,x4]被覆蓋了。這時區間的被覆蓋次數:

\(cnt_=1\),\(cnt_=2\),\(cnt_=1\)。

那麼我們只需要統計\(cnt\)是正數的區間,然後將這些區間的長度相加即可得到\(len\)。

此時\(len=l_+l_+l_=150\)。

\(r_2\)與下一條邊的距離\(h=50\),則第二塊面積​(綠色)\(s_2=len\times h\)

當掃到邊\(r_3\)時,我們知道這是左邊那個矩形的上邊,它的標記是-1。

被它覆蓋的區間有[x1,x2]和[x2,x3]。

遇到\(r_3\)意味著我們不再需要考慮\(r_1\)的長度了,如何在計算中體現這個「不再考慮」?很簡單,因為我們之前統計過區間的被覆蓋次數,「不再考慮」時將對應區間的覆蓋次數扣除(-1)即可。

更新之後,區間的被覆蓋次數如下:

\(cnt_=0\),\(cnt_=1\),\(cnt_=1\)。

同樣,我們再一次統計\(cnt\)是正數的區間,然後將這些區間的長度相加即可得到\(len\)。

此時\(len=l_+l_=100\)。

\(r_3\)與下一條邊的距離\(h=55\),則第三塊面積(紅色)\(s_3=len\times h\)

我們知道\(r_4\)是整個圖里剩下的最後一條與x軸平行的邊,所以當掃瞄線掃過\(r_3\)時,面積計算就結束了,不需要再掃過\(r_4\)。

那麼最後的面積並\(ans=s_1+s_2+s_3\)

過程捋清楚了,那麼落實到**上,又應該用什麼資料結構來儲存最關鍵的、由所有矩形的端點在x軸上的投影所劃分出的區間呢?

在上述過程中,我們的計算都是具體到如[x1,x2]、[x2,x3]這樣的最小區間。這顯然是可優化的:例如,在掃瞄到r_3時,可以只在[x1,x3]這個更大區間上作統計,而不必進一步細分。

這樣的性質可以聯想到線段樹

在模板線段樹中,乙個結點代表的是區間\([l,r]\)上的若干個點。特別地,在葉子結點中\(l=r\),此時\([l, r ]\)代表的是1個點。

本題要求我們線段樹存的是線段,不能是點。所以我們改變結點的定義:結點\([l,r]\)代表的是線段\([l,r+1]\)。如此,線段樹的葉子節點\([l,r](l=r)\)代表的就是最小的區間,如\([x_1,x_2]\)、\([x_4,x_5]\)這樣的區間。

還有乙個問題:離散化。考慮題目中的資料範圍:1e9,顯然這要求我們對所有矩形的端點的x值進行離散化(也就是重新編號)。再將實際x值儲存到離散化後對應下標即可,如x[1]=100,x[2]=150.

struct treetree[maxn*4];
建樹過程和模板線段樹區別不大:

void build(int i,int l,int r)
掃瞄到新的一條與x軸平行的邊後的對len的更新操作(最後的更新結果是tree[1].len):

void update(int i)

}

void change(int i,ll l,ll r,int c)

//如果下標代表的區間被[l,r]部分覆蓋,找子結點,再計算新的len

change(i*2,l,r,c);

change(i*2+1,l,r,c);

update(i);

}

struct scanline

}line[maxn*2];

struct tree tree[maxn*4];

void build(int i, int l, int r)

void update(int i)

}void change(int i, ll l, ll r, int m)

change(i * 2, l, r, m);

change(i * 2 + 1, l, r, m);

update(i);

}int main()

//顯然我們讀入了2*n條邊

n *= 2;

sort(line + 1, line + n + 1);

//將所有線從低到高排序

sort(x + 1, x + n + 1);

int tail = unique(x + 1, x + n + 1) - (x + 1);

//離散化第二步:去重(unique函式要求陣列有序)

build(1, 1, tail - 1);

//考慮線段樹結點的定義,就知道最右邊那個端點不應該包含在內

//故範圍是1到tail-1

ll ans = 0;

//從下往上掃

for (int i = 1; i < n; i++)

cout << ans;

return 0;

}

P5490 模板 掃瞄線 掃瞄線

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

P5490 模板 掃瞄線

n 給定n nn個矩形左下角和右上角的座標,求該矩形面積並 資料範圍 n 1 05n leq 10 5 n 105sol utio nsolution soluti on將每個矩形看做兩條平行於y yy軸的線段,掃瞄過去即可 需要注意的是給出來的是點,而我們維護的是線段 時間複雜度 o n log ...

P5490 模板 掃瞄線

掃瞄線的過程不做多述 主要講解 實現,沒有深刻理解是無法完全打出來的 首先離散化x座標,因為我們要用線段樹進行維護,x座標過大,陣列範圍是不允許的 再考慮線段樹怎麼維護 假如維護k 1,3 兩個子節點k1 1,2 k2 3,3 假如k1的len x 2 x 1 那k2的len x 3 x 3 k2此...