Segment/Interval Tree - 2
by 林品諺
強,還要更強 強,還要更強
• 我們現在已經會的東西有:
• 單點修改、區間詢問
• 區間修改、單點詢問
• 單點修改、單點詢問?
• 陣列 O(1) 解決
• 區間修改、區間詢問?
• 怎麼好像有點困難 QAQ
範例題目 範例題目
• 給你一個長度為 N 的序列以及 Q 個操作,操作分為兩種:
• 1. 把一個區間的每個數字都加上 x
• 2. 詢問一個區間的最大值
• N, Q ≤ 100,000
題目說明 題目說明
• 原始序列:
8 7 1 2 2 8
題目說明 題目說明
• 詢問 [3, 6] 的最大值: 8
8 7 1 2 2 8
題目說明 題目說明
• 把 [2, 5] 加上 7 :
8 14 8 9 9 8
題目說明 題目說明
• 詢問 [3, 6] 的最大值: 9
8 14 8 9 9 8
just try it just try it
• 先用我們已經會的線段樹知識嘗試一下
• 每個節點記錄它所對應的區間的最大值
• 區間詢問:一樣找到那 logN 個節點問一問答案, O(logN)
• 區間修改:把它轉成 N 個單點修改, O(NlogN)
• 複雜度爆炸了!
• 把題目轉成區間修改跟 N 個單點詢問顯然也沒有解決問題
• 如果那 N 個東西都被加上了同個數字,我們真的有需要把它當成 N 個單點修改來操作嗎?
名詞解釋 名詞解釋
• 「一個區間所對應的節點」
• 所有被該區間完全包含的節點們當中,深度最淺的那些節點
• 也就是區間詢問時會問到的那些節點
有的時候你不需要那麼勤勞 有的時候你不需要那麼勤勞
• 想想區間修改、單點詢問
• 把修改放在該區間所對應的節點上
• 詢問的時候會經過該點的祖先們,這時再去看剛剛的那些修改
• 區間詢問是不是也可以套用類似的想法?
• 對於每個修改,把它放在該區間所對應的節點上
• 區間修改:找到對應的節點們然後把修改貼上去, O(logN)
• 區間詢問:找到對應的節點們,把所記錄的 data 值加上修改的 值後取 max , O(logN)
圖解 圖解
• 詢問 [3, 6] 的最大值: 8
8 7 1 2 2 8
(0, 8)
(0, 8) (0, 8)
(0, 8) (0, 7) (0, 2) (0, 8)
(0, 7) (0, 1) (0, 2) (0, 8)
圖解 圖解
• 把 [2, 5] 加上 7
8 14 8 9 9 8
(0, 14)
(0, 14) (0, 9)
(0, 8) (7, 7) (7, 2) (0, 9)
(0, 7) (0, 1) (7, 2) (0, 8)
圖解 圖解
• 詢問 [3, 6] 的最大值: 9
8 14 8 9 9 8
(0, 14)
(0, 14) (0, 9)
(0, 8) (7, 7) (7, 2) (0, 9)
(0, 7) (0, 1) (7, 2) (0, 8)
實作 實作
• chg: 修改的值 , data: 我們要維護的東西 ( 最大值 )
區間修改 區間修改
[a, b]: 詢問區間 , i: 節點編號 , [l, r]: 該節點對應的區間 , x: 要加上去的數字
區間詢問 區間詢問
故事總是有個轉折 故事總是有個轉折
• 有了剛剛的技巧,看起來什麼區間加值、區間改值什麼的我們都 會做了
• 那如果兩個一起來呢?
例題二 例題二
• 給你一個長度為 N 的序列以及 Q 個操作,操作分為兩種:
• 1. 把一個區間的每個數字都加上 x
• 2. 詢問一個區間的最大值
• 3. 把一個區間的每個數字都變成 x
• N, Q ≤ 100,000
思考一陣 思考一陣
• 這不是一樣ㄇ……等等,真的一樣嗎?
• 如果開兩個變數分別存兩種修改,那我怎麼知道是先改再加、還 是先加再改?
• 到頭來好像又只能一個一個改了。
• 難道我們就要在這裡停下腳步了嗎?
繼續思考 繼續思考
• 問題發生在,兩種修改會互相影響。
• 假設所有加值操作都在左半邊,所有改值操作都在右半邊,那麼 就直接做就好了,對我們而言這世界就依舊美好。
• 兩種操作不影響的時候,就沒有不需要把修改壓到底了。
• 每一次修改只會動到 O(logN) 個節點。
• 如果我們在修改的時候,才順便把遇到的修改壓下去…?
• …… 好像發現了什麼?
不小心得到了一個做法 不小心得到了一個做法
• 修改的部分跟剛剛一樣,先放在該區間對應的節點上。
• 如果遞迴的路上發現這個節點有其他的修改資訊,就把那些資訊 往下推一層。
• 因為每次修改還是只會走過 O(logN) 個節點,所以複雜度不變。
• 詢問就照樣回答。
順序問題 順序問題
• 剛剛的做法還有一個細節要注意,那就是兩種修改值的順序問題
。
• 一個常見的做法是定義一個順序,比如說如果兩個修改值皆非 0 ( 或任何表示他沒被修改的數字 ) 時,假設是先改再加。
• 加值的時候就直接加上去。
• 改值的時候,因為改值這件事本身就會優先於加值,所以做改值 操作的時候要把加值的修改值改成 0 。
• 就這題而言,定義順序是先加再改不太能做。
實作 實作
• chg1: 加值的修改 , chg2: 改值的修改 ( 以 chg1=0, ch2=0 表示沒有修改 )
把修改往下壓 把修改往下壓
• 1
區間加值 區間加值
• 1
區間改值 區間改值
• 1
區間詢問 區間詢問
• 1
懶人標記 懶人標記
• 剛剛的技巧被稱為「懶人標記」 (lazy tag) 。
• 修改就直接先放在那,要用到的時候就路過順便壓一下。
• 可分治性
• 要能夠把修改先放著,就必須能夠在修改沒壓下去的時候就回答 詢問。
• 懶真是人類進步的原動力
• 只在你需要做事的時候才做事,有時候你其實不需要那麼勤勞。