線段樹複習筆記
\[\Large\rm 演算法簡介 \]
\(\quad\)線段樹是 \(\rm OI\) 中最重要的資料結構,有很多分支,非常重要。
\(\large\rm 普通線段樹\)
\(\quad\)建立一些結點,每個節點儲存序列中一段的資訊,形成滿二叉樹的結構。
\(\large\rm 動態開點\)
\(\quad\)我們發現如果對所有位置都建立一個節點太浪費空間,於是不讓子節點為父節點編號的兩倍,而是在需要的時候給子節點分配編號,對每個節點記 \(\rm lc\) 和 \(\rm rc\),然後記 \(\rm cnt\) 表示當前使用到哪一個結點,這樣就可以實現了。
\(\large\rm 權值線段樹\)
\(\quad\)將權值作為下標,節點記憶體儲的是這個權值有多少個。大多數時候值域很大,所以要使用動態開點或離散化。
\(\large\rm 線段樹合併\)
\(\quad\)一般是將一顆線段樹合併到另一顆上。實現時同時列舉兩顆線段樹的結點,如果當前有一個節點不存在,就令被合併線段樹當前結點為存在的那個結點,否則遞迴。
\(\large\rm 掃描線\)
\(\quad\)由於掃描線一般都是用線段樹維護,所以放在這裡面講。
\(\quad\)一般用於維護偏序關係,列舉其中一維有序狀態,用線段樹維護另一維。
\(\quad\)三維偏序關係還可以套一個 cdq 分治
\[\Large\rm 習題 \]
\(\large\rm [P5490]掃描線\)
\(\quad\)掃描線模板題,從小到大列舉橫線的縱座標,列舉到矩形下界就給這條邊的左右端點構成的區間 \(+1\),否則 \(-1\),用線段樹維護,當前線段樹有值的結點個數就是這個縱座標的貢獻。
\(\quad\)怎麼維護線段樹中不為 \(0\) 的位置數量呢?我們發現在做掃描線的時候權值不會小於 \(0\),於是我們考慮維護區間最小值和最小值的數量,這個在合併的時候,如果左右兒子的最小值相同,則將其最小值數量加起來,否則繼承最小值更小的那個的最小值數量。
\(\quad\)當然,本題還需要動態開點,所以需要維護區間內有效點的數量。
\(\quad\)時間複雜度 \(\Theta(n\log n).\)
\(\large\rm [SPOJ]GSS-I\)
\(\quad\)先求出字首和,發現答案為區間最大值減最小值,但需要滿足最小值在最大值右邊。
\(\quad\)對線段樹的每個節點維護最小值,最大值和答案,發現答案可以從兩棵子樹的答案更新,也可以更新為右子樹中的最大值減去左子樹中的最小值。
\(\quad\)於是直接用線段樹即可維護,時間複雜度 \(\Theta(n\log n).\)
\(\large\rm [SPOJ]GSS-III\)
\(\quad\)發現這題是帶修版的 \(\rm GSS1\),但是由於字首和無法動態修改,於是考慮直接維護區間和。
\(\quad\)顯然要維護區間和以及最大子段和,發現最大子段和需要通過左兒子的最大字尾和和右兒子的最大字首和更新,於是再記最大前後綴和,而最大前後綴和是可以通過區間和和子節點的最大前後綴和計算的,於是這題就可以用線段樹做了。
\(\quad\)時間複雜度 \(\Theta(n\log n).\)
\(\large\rm [SPOJ]GSS-IV\)
\(\quad\)我們發現,一個區間內如果全都是 \(1\) 或 \(0\),那麼對它開方是沒有意義的,所以記錄最大值,如果最大值為 \(0\) 或 \(1\),那麼直接跳過這個區間。
\(\quad\)我們發現這樣做每個點最多被修改 \(7\) 次,故時間複雜度 \(\Theta(n\log n).\)
\(\large\rm [SPOJ]GSS-V\)
\(\quad\)我們發現這個問題和 \(\rm GSS-I\) 很像,唯一的區別是左右兩邊限定了區間。我們發現如果兩個區間不重合,那就是右區間最大值減左區間最小值。
\(\quad\)若兩區間重合,令從左到右三個區域分別為 \(\rm I,II,III\),首先用 \(\rm \max\{II\cup III\}-\min\{I\}\) 更新答案,然後用 \(\rm \max\{III\}-\min\{I\cup II\}\) 更新答案,最後只剩下兩個端點都在 \(\rm II\) 內的答案,這個就是 \(\rm GSS-I.\)
\(\quad\)時間複雜度 \(\Theta(n\log n).\)
\(\large\rm [P4198]樓房重建\)
\(\quad\)神題。
\(\quad\)首先肯定將每個樓房的資訊轉化為斜率。顯然要維護區間最大值和從這個區間左端點開始能看到多少棟樓。
\(\quad\)現在考慮將左右區間合併,顯然要先將左區間的答案加上,然後考慮右區間能看到多少個點。
- 如果左區間的最大值小於右區間的左區間的最大值,那麼肯定對右區間的右區間沒有影響,將右區間的右區間的答案加上,左區間遞迴。
- 如果左區間的最大值大於右區間的左區間的最大值,那麼右區間的左區間肯定一個都看不到,直接將右區間的右區間遞迴下去。
\(\quad\)單次合併時間複雜度 \(\Theta(n\log n)\),總時間複雜度 \(\Theta(n\log ^2n).\)
\(\quad\rm PS:\) 這題有個坑點,計算右區間的右兒子的答案的時候不能寫成 ans[RS]
,而是要寫成 ans[p] - ans[LS]
,因為右兒子的答案可能會被左兒子擋掉一些,這些還是不能算進去。
\(\large\rm [P1502]視窗的星星\)
\(\quad\)考慮將每顆行星變成一個帶權矩形,我們發現最大亮度和就是最大矩形交的權值之和。
\(\quad\)考慮用掃描線,維護區間最大值即可。
\(\quad\)坑點 \(1:\) 由於邊界上的點不用算,所以矩形右上角為 \((x+w-1,y+h-1).\)
\(\quad\)坑點 \(2:\) 我們在每次修改過後都統計了一遍答案,所以當線段縱座標相同的時候,刪除要排在前面,防止統計了一個不合法的大的答案。
\(\large\rm [CF240F]TorCoder\)
\(\quad\)考慮一次操作幹了什麼事情,若存在一個以上字母出現了奇數次,則無法重排,否則將出現奇數次的字母放在最中間,然後將所有字母按照從小到大再從大到小的順序放在這個字母的兩邊。
\(\quad\)於是可以對每個字母開一顆線段樹,維護區間賦值操作和區間求數量操作即可。
\(\quad\)時間複雜度 \(\Theta(n\log n|\Sigma|).\)
\(\large\rm [CF242E]XOR~on~Segment\)
\(\quad\)考慮對每一位開一顆線段樹,問題就變成了維護一個 \(0/1\) 序列,支援區間取反和區間求和。
\(\quad\)時間複雜度 \(\Theta(n\log^2n).\)
\(\large\rm [CF414C]Mashmokh~and~Reverse~Operation\)
\(\quad\)我們發現這個題目中區間的結構很像一顆線段樹,於是考慮它的性質。
\(\quad\)發現交換一個翻轉一個區間等價於將它的兩個子區間交換,然後遞迴操作。而一個區間的逆序對數量為兩個子區間之間的逆序對數量加上兩個子區間內部的逆序對數量。
\(\quad\)於是考慮維護每一層每一個結點的兩個子區間之間的逆序對數量之和。這樣,修改操作就變成了將某一層下面的所有區間翻轉,由於層數只有 \(20\),所以直接維護每一層是否被翻轉即可。求當前的答案只需要預處理每一層的所有節點的兩個子區間之間的答案之和即可,發現一層區間一定是一起翻轉,於是每一層只有兩種狀態,分別預處理即可。
\(\quad\)時間複雜度 \(\Theta[(2^n+m)n].\)
\(\large\rm [CF446C]DZY~Loves~Fibonacci~Numbers\)
\(\quad\)擷取 \(\rm CF\) 官方題解 :
\(\quad\)As we know, \(F_{n}=\frac{\sqrt{5}}{5}\left[\left(\frac{1+\sqrt{5}}{2}\right)^{n}-\left(\frac{1-\sqrt{5}}{2}\right)^{n}\right]\)
\(\quad\)Fortunately, we find that \(383008016^{2} \equiv 5\left(\bmod 10^{9}+9\right)\)
\(\quad\)So, \(383008016 \equiv \sqrt{5}\left(\bmod 10^{9}+9\right)\)
\(\quad\)With multiplicative inverse, we find,
\(\quad\frac{\sqrt{5}}{5} \equiv 276601605\left(\bmod 10^{9}+9\right)\)
\(\quad\frac{1+\sqrt{5}}{2} \equiv 691504013\left(\bmod 10^{9}+9\right)\)
\(\quad\frac{1-\sqrt{5}}{2} \equiv 308495997\left(\bmod 10^{9}+9\right)\)
\(\quad\)Now, \(F_{n} \equiv 276601605\left(691504013^{n}-308495997^{n}\right)\left(\bmod 10^{9}+9\right)\)
\(\quad\)於是我們發現只需要分別維護兩個序列,在一個序列中公比相同,故只需要維護首項,後面的數可以直接算出來。下傳的時候給左子區間直接加上這個首項,右子區間加首項乘上 \(q^{\frac{r-l+1}{2}}\) 即可。
\(\quad\)時間複雜度 \(\Theta(n\log n).\)
\(\large\rm [CF522D]Closest~Equals\)
\(\quad\)我們發現,如果記 \(p_i\) 表示距離 \(i\) 最近的在 \(i\) 之前的與 \(i\) 顏色相同的點與 \(i\) 的距離,那麼對於一個詢問 \([l,r]\),其答案為所有滿足 \(i-p_i\geqslant l\) 和 \(i\leqslant r\) 的點中,\(p_i\) 最小的那個。
\(\quad\)於是考慮將座標離散化,然後將詢問離線下來,按右端點排序,一個個列舉 \(i\),若 \(p_i\not =0\),則將 \(C_{i-p_i}\) 的值與 \(p_i\) 取 \(\min\),可以發現,\(C\) 中區間 \([l,r]\) 內的最小值就是這個區間的答案。
\(\quad\)單點修改,區間查詢,可以使用線段樹維護。可以發現 \([l,r]\) 的答案和 \([l,n]\) 的答案是相同的,於是也可以使用樹狀陣列維護字尾和。
\(\quad\)時間複雜度 \(\Theta(n\log n).\)