Problem Statement
題目簡述
有 N N N 個格子排成一列,初始全白。
依序進行 Q Q Q 次操作,每次給定整數 L i , R i L_i, R_i L i , R i ,將區間 [ L i , R i ] [L_i, R_i] [ L i , R i ] 內的所有格子塗黑(已黑者保持黑)。
每次操作後,請輸出當前仍為白色 的格子數量。
Constraints
約束條件
1 ≤ N ≤ 1 0 9 1 \leq N \leq 10^9 1 ≤ N ≤ 1 0 9
1 ≤ Q ≤ 2 × 1 0 5 1 \leq Q \leq 2 \times 10^5 1 ≤ Q ≤ 2 × 1 0 5
1 ≤ L i ≤ R i ≤ N 1 \leq L_i \leq R_i \leq N 1 ≤ L i ≤ R i ≤ N
所有輸入皆為整數。
思路:離散化 + Lazy Segment Tree
正難則反:轉換問題目標
隨著操作進行,白色部分會被切割得支離破碎,因此維護白色格子的數量較為困難。
相對地,塗黑 操作是明確的區間覆蓋,故維護目前被塗黑的格子總數 (記為 B B B )會相對容易。
每次查詢的答案即為:
白色格子數 = N − B \text{白色格子數} = N - B
白色格子數 = N − B
核心解法:離散化 + 線段樹
這是一道典型的區間覆蓋問題,但由於 N N N 高達 1 0 9 10^9 1 0 9 ,無法直接建立長度為 N N N 的陣列。
觀察到操作次數 Q Q Q 僅 2 × 1 0 5 2 \times 10^5 2 × 1 0 5 ,且塗色操作只與給定的端點有關,這提示我們使用 離散化 配合 線段樹(Segment Tree) 來解決。
為什麼可以維護區間?(離散化原理)
染黑操作的本質是區間聯集。顏色的變化(由白變黑)只會發生在查詢區間的邊界處。
我們將所有相關的座標點收集起來:對於每個查詢 [ L i , R i ] [L_i, R_i] [ L i , R i ] ,將 L i L_i L i 和 R i + 1 R_i+1 R i + 1 加入集合。
為什麼要用左閉右開區間
[ L , R + 1 ) [L, R+1) [ L , R + 1 ) ?
使用半開區間的主要好處是:區間邊界不會重疊 ,使得相鄰區間可以無縫拼接,且長度計算統一為 R − L R - L R − L 。
舉例說明 :
假設有兩個操作:塗黑 [ 3 , 5 ] [3, 5] [ 3 , 5 ] 和 [ 5 , 7 ] [5, 7] [ 5 , 7 ] (共享端點 5 5 5 )。
閉區間做法 :收集端點 { 3 , 5 , 5 , 7 } → { 3 , 5 , 7 } \{3, 5, 5, 7\} \to \{3, 5, 7\} { 3 , 5 , 5 , 7 } → { 3 , 5 , 7 } 。
形成的區間段:[ 3 , 5 ] [3, 5] [ 3 , 5 ] 、[ 5 , 7 ] [5, 7] [ 5 , 7 ] 。
問題:格子 5 5 5 同時是前一段的右端點與後一段的左端點,在處理時容易重複計算 或需要額外判斷。
半開區間做法 :將 [ 3 , 5 ] [3, 5] [ 3 , 5 ] 轉換為 [ 3 , 6 ) [3, 6) [ 3 , 6 ) ,[ 5 , 7 ] [5, 7] [ 5 , 7 ] 轉換為 [ 5 , 8 ) [5, 8) [ 5 , 8 ) 。
收集端點 { 3 , 6 , 5 , 8 } → { 3 , 5 , 6 , 8 } \{3, 6, 5, 8\} \to \{3, 5, 6, 8\} { 3 , 6 , 5 , 8 } → { 3 , 5 , 6 , 8 } 。
形成的區間段:[ 3 , 5 ) [3, 5) [ 3 , 5 ) 、[ 5 , 6 ) [5, 6) [ 5 , 6 ) 、[ 6 , 8 ) [6, 8) [ 6 , 8 ) ,長度分別為 2 , 1 , 2 2, 1, 2 2 , 1 , 2 。
每個區間段的邊界不重疊,長度即為端點差,且自動處理了重疊部分。
將收集到的座標排序去重後,得到序列 X 0 < X 1 < ⋯ < X m X_0 < X_1 < \dots < X_m X 0 < X 1 < ⋯ < X m (共 m + 1 m+1 m + 1 個點)。
這些座標點將整個數線切分成了 m m m 個基本區間 [ X j , X j + 1 ) [X_j, X_{j+1}) [ X j , X j + 1 ) (對應程式碼中 len(Xs)-1)。
對於任意一個基本區間,不可能有操作只操作區間的其中一部份。
因此,我們可以將每個區間 [ X j , X j + 1 ) [X_j, X_{j+1}) [ X j , X j + 1 ) 視為一個不可分割的單元(葉節點),其長度固定為 X j + 1 − X j X_{j+1} - X_j X j + 1 − X j 。
線段樹中維護了哪些資訊?
我們使用 Lazy Segment Tree 來維護離散化後的區間,目標是即時查詢「被染黑的總長度」B B B ,答案即為 N − B N - B N − B 。
欄位
意義
特性
b
該區間內已被染黑的長度
動態更新
w
該區間的總長度
建樹後固定
LazySegTree 介面定義(ACL)
使用 LazySegTree 需定義以下元素:
元素
型別
本題定義
說明
op(a, b)
S × S → S S \times S \to S S × S → S
( b 1 + b 2 , w 1 + w 2 ) (b_1+b_2,\; w_1+w_2) ( b 1 + b 2 , w 1 + w 2 )
合併兩個子區間狀態
e
S S S
( 0 , 0 ) (0, 0) ( 0 , 0 )
狀態單位元素
mapping(f, s)
F × S → S F \times S \to S F × S → S
( w , w ) (w, w) ( w , w ) if f = 1 f=1 f = 1 else ( b , w ) (b, w) ( b , w )
Tag 作用於狀態
composition(f, g)
F × F → F F \times F \to F F × F → F
f ∨ g f \lor g f ∨ g
Tag 合成
id
F F F
0 0 0
Tag 單位元素(無操作)
註 :一旦染黑就不會變回白,重複染黑也不會有額外效果,故 Tag 合成使用 Bitwise OR 即可。
複雜度分析
時間複雜度:O ( Q log Q ) \mathcal{O}(Q \log Q) O ( Q log Q )
離散化需排序座標,而座標的數量最多為 2 Q 2Q 2 Q ,耗時 O ( Q log Q ) \mathcal{O}(Q \log Q) O ( Q log Q ) 。
線段樹葉節點數最多 2 Q − 1 2Q-1 2 Q − 1 ,建樹 O ( Q ) \mathcal{O}(Q) O ( Q ) 。
每次查詢進行區間更新,耗時 O ( log Q ) \mathcal{O}(\log Q) O ( log Q ) ,總共 O ( Q log Q ) \mathcal{O}(Q \log Q) O ( Q log Q ) 。
空間複雜度:O ( Q ) \mathcal{O}(Q) O ( Q )
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from itertools import pairwisefrom atcoder.lazysegtree import LazySegTreedef solve (): n, q = map (int , input ().split()) queries = [tuple (map (int , input ().split())) for _ in range (q)] Xs = set () for l, r in queries: Xs.add(l) Xs.add(r + 1 ) Xs = sorted (Xs) mapX = {val: i for i, val in enumerate (Xs)} data = [(0 , y - x) for x, y in pairwise(Xs)] op = lambda a, b: (a[0 ] + b[0 ], a[1 ] + b[1 ]) mapping = lambda f, s: (s[1 ], s[1 ]) if f == 1 else s composition = lambda f, g: f | g seg = LazySegTree(op, (0 , 0 ), mapping, composition, 0 , data) for l, r in queries: L, R = mapX[l], mapX[r + 1 ] seg.apply(L, R, 1 ) print (n - seg.all_prod()[0 ]) if __name__ == '__main__' : solve()