「線段樹」總結

普通線段樹數組

基於將區間拆分爲$O(logn)$區間的並這一思想來實現。其中附加信息的維護方式變化很靈活。函數

  • 區間加。利用懶標記直接維護。
  • 區間加&區間乘。兩個懶標記同時維護,其中乘法更新要帶動加法更新。
  • 區間染色。一個懶標記直接覆蓋便可。
  • 區間gcd。
  • 樹上。有點相似樹剖,直接利用DFS序轉化爲線性,而後普通維護就行了。
  • 維護最小值的位置。query函數的類型做爲結構體,每次返回最小值及其位置。
  • 利用取模以後縮小一半的性質,一個數$a$最多被模$loga$次。所以暴力作便可。還要須要維護最大值判斷不用取模的狀況。

Codeforces 292 E. Copying Data優化

給出兩個序列$a,b$。動態對$b$進行修改,修改操做是指定$a$的一段區間$[x,x+k]$,粘貼到$b$的$[y,y+k]$上。單點詢問$b$。spa

若是咱們知道某一個位置最後一次是被哪一次操做覆蓋的,那麼就能夠知道這個位置對應的值。排序

由此問題轉化爲求解某一個位置最後一次被哪一個操做覆蓋。這顯然是一個區間賦值(染色)問題。利用線段樹維護——只須要用懶標記強行覆蓋便可。繼承

Codeforces 914 D. Bash and a Tough Math Puzzle遞歸

給出一個序列。動態單點更新。每次詢問一段區間,並給出一個數字$x$。問能不能經過修改區間內的一個數字,使區間的$gcd=x$。io

這道題告訴我gcd也是知足區間性質的。假如一段區間內全部數都是$x$的倍數,那麼我能夠將其中一個修改成$x$使得全部數的$gcd$爲$x$;假如一段區間內只有一個數不是$x$的倍數,那麼我能夠將那個數修改成$x$使得全部數的$gcd$爲$x$;假如一個區間內有超過一個數不是$x$倍數則無解。由此解決這個問題只須要統計區間內有多少個數不是$x$的倍數便可。擴展

咱們能夠像維護區間和同樣來維護區間$gcd$。若是一個區間的$gcd$是$x$的倍數,意味着這裏面全是$x$的倍數;不然,繼續深究,若是到達葉節點那麼直接判斷並返回。注意,這樣的作法違背了線段樹$O(logn)$查詢的原則,因此須要作一些優化——一旦總數超過1就跳出。複雜度玄學。gc

Codeforces 877 E. Danil and a Part-time Job

給出一棵樹,點權爲bool。動態詢問子樹和,同時會對點權進行修改。修改的規則是:選擇一棵子樹$v$,子樹中全部節點的點權都異或1。

用DFS序將問題轉化爲線性。而後考慮如何完成取反操做。很像Splay裏面的那個區間翻轉操做,所以咱們的標記的更新時異或1便可。而後將總和與總數做差便可。

Codeforces 383 C. Propagating tree

給出一棵樹,有點權,動態查詢子樹和。其中會對點權進行修改,修改的規則是:選擇一顆子樹$v$,給$v$這個點+k,$v$的直接兒子-k,$v$的孫子+k……以此類推。

首先利用樹的DFS序轉化爲線性問題。關鍵在於分層有正負的問題。雖然哪一層正哪一層負不肯定,可是相對正負關係必定是肯定的。咱們能夠假設奇數層爲正,偶數層爲負。假設偶數層要更新爲正,則更新一個負數。最後統計的時候依然按照奇數正偶數負的方式來統計就能獲得正確答案。這種思想感受仍是很神奇很重要的。

樹的深度統計是要放在遞歸以前作的,這裏我寫掛了。

Codeforces 438 D. The Child and Sequence

給出一個序列$a_i$($n \leq 10^5, a_i \leq 1e9$),動態詢問區間和,單點修改,修改操做是區間取模。

區間取模貌似不能直接維護。事實上咱們能夠暴力,加上一點小小的優化。假設咱們要讓一段區間對$P$取模。那麼若是區間內的最大值小於$P$,那麼可忽略此操做。另外咱們發現,一個數取模之後至少縮小一半。由此$a_i$最多被模$loga_i$次。由此,複雜度近似是$O(nlognlog1e9)$。

取模以後縮小一半的性質證實:設$a%b=c$,其中$a>b>c$。可表示爲$a=kb+c$,其中$k\geq 1$。由於$b>c,k \geq 1$,因此$kb>c$。因此$a>2c$

 


主席樹

單點更新僅產生$O(logn)$個點。故複雜度爲$O(mlogn)$

(一)維護區間第$k$小。經過維護區間內每一個數的出現次數,而後在線段樹上二分實現。所謂區間,就是指兩個歷史版本的差。這裏利用了前綴和的思想。

(二)維護樹上路徑上的第$k$小。轉化爲路徑上每一個數的出現次數。利用樹上差分的思想。

Codeforces 961 E. Tufurama

給出一個序列$a_i$($a_i \leq 1e9$),問有多少對$(x,y)$知足$a_x \geq y$且$a_y \geq x$。其中$1 \leq x<y \leq n$

對於每個$y$,考慮$x∈[1,y-1]$。那麼$x$要知足的條件有$x \leq a_y$,$a_x \geq y$。由前者結合前提可得$x∈[1,min\{y-1,a_y\}]$。

至此,問題轉化爲求解$x∈[1,min\{y-1,a_y\}]$裏,$a_x \geq y$的$x$個數。這是一個求解區間大於$k$的個數問題,利用主席樹解決便可。注意須要離散化,細節不少。

 


一類特殊的出現次數問題

Codeforces 813 E. Army Creation

給出一個序列$a_i$($n,a_i \leq 10^5$),動態詢問區間內元素的最大個數,知足相同元素不超過$k$個。

不能維護每一個元素出現次數,這樣的話很是難$log(n)$統計答案。這裏有一個巧妙的轉化:預處理出$a_i$以後的第$k$個元素的位置,記爲$b_i$。若是$b_i \leq r$,那麼就不選$a_i$;若是$b_i > r$,那麼就選。這樣很巧妙地限制了一段區間內咱們最多隻取靠後的$k$個相同元素。所以咱們利用$b_i$創建主席樹,問題轉化爲求解區間內大於$r$的元素個數,至關於求$(r,+\infty)$的區間和。

luogu 1972 HH的項鍊

給出一個序列$a_i$($n,a_i \leq 5·10^5$),動態詢問區間不一樣的元素個數。

容易發現這就是上面那題$k=1$的特殊狀況。理論上直接主席樹解決便可。

但咱們考慮一種線段樹的離線作法。對於一個區間的右邊界,若是咱們只統計每種元素最靠近右邊界的那個位置,就不會重複了,這樣區間和就是不一樣元素的個數了。咱們能夠這樣解決:預處理出與$a_i$相等的元素上一次出現的位置$las_i$。將全部詢問區間按照右端點排序,用線段樹維護每一個位置,0表示不算,1表示算。右端點往右擴展時,碰到的這個數若是以前出現過,就在先前那個位置將1改成0。這樣就保證了只統計最靠後的。

Codeforces 1000 F. One Occurrence

給出一個序列$a_i$($n,a_i \leq 5·10^5$),動態詢問一個區間內只出現一次的數(多解可任意)。

考慮線段樹離線(或主席樹)。咱們一樣只考慮最靠近右邊界的,對於這樣的$a_i$,若是上一次出現的位置$<L$,那麼它必定只出現一次。因而咱們只須要記錄每一個位置上的數的上一次出現位置,用線段樹維護最小值以及最小值的位置便可。

Codeforces 833 B. Bakery

給出一個序列$a_i$($n,a_i \leq 35000$),要求把序列劃分爲$k$段($k \leq 50$),每段的價值爲其中不一樣的數的個數。求最大總價值。

明顯是一個劃分DP的模型。有$dp_{i,j}=max\{dp_{k,j-1}+distinct(k+1,i)\}$。直接轉移須要枚舉$k$,複雜度就是$O(kn^2)$級別的。所以須要考慮別的方法。

考慮已知$dp_{k,j-1}$,要考慮它對轉移的貢獻就是要求出全部的$distinct(k+1,i)$。考慮對於區間$(k,i]$內的一個位置$p$能不能對它產生貢獻——一種數值的數最多隻能產生一個貢獻,不如規定最左側的數產生貢獻。這就讓咱們聯想到去維護一個$pre_p$,咱們只考慮$pre_p \leq k$的位置。針對這種問題,咱們讓每個$p$去覆蓋區間$[pre_p,p)$。因而位置$k$被覆蓋了幾回就表明了$distinct(k+1,i)$。用線段樹維護區間修改和最大值查詢便可。

 

總結

這類要維護區間內出現了多少個不一樣的數的問題,咱們的處理方法都是:對於重複的元素,只看其中一個。或是最左側的,或是最右側的。判斷最左最右的方法就是維護一個$pre$或者$nxt$數組。線段樹維護的通常是位置,附加值表示這個位置算不算或者總共被後面覆蓋幾回之類的。

 


可持久化數組

可經過主席樹來維護。數組就是主席樹的葉節點,上面那些節點實際上是不存放附加信息的。

 

 


線段樹合併

在樹上要求解各子樹的答案時,而且用線段樹維護時,要合併子樹的信息來獲得整個樹的信息,這時就要用到線段樹合併。

線段樹合併的思想很簡單,就是合併。考慮到空間的限制,要求動態開點(時空複雜度$O(n \log n)$)

當合並根節點爲$a,b$的兩個線段樹時,有兩種代碼實現的方法。一種是直接將合併後的結果放在$a$上,一種是每次合併新開一個節點做爲新的根。對於題目僅僅要咱們輸出一次每一個節點的答案時,可使用第一種方法,由於咱們不須要歷史信息。而若是題目是在線的屢次詢問,那麼此時顯然須要利用到歷史信息的,此時就要選擇方法二了。由於第一種方法咱們會在合併的時候直接繼承某一個子樹,這樣在modify操做的時候就會直接影響到歷史的線段樹致使答案錯誤了。

Codeforces 600 E. Lomsat gelral

給出一顆樹,每一個節點有一個顏色。詢問每一個子樹內顏色數量最多的那些顏色的總和(顏色的值)

用線段樹維護最大值,再附加維護全部最大值的顏色總和。線段樹合併便可。

luogu 3605 Promotion Counting

給出一顆樹,每一個點有一個權值。詢問每一個節點,其全部子樹內的節點中權值比它大的節點個數。

離散化後,權值線段樹維護和,線段樹合併便可。

luogu 3899 談笑風生

給出一棵樹,每次詢問一個$a$節點,問有多少個有序數對$(a,b,c)$,知足$a,b$都是$c$的祖先,且$a,b$之間距離不超過$k$

顯然要用線段樹來維護某一個深度的節點,關鍵是維護什麼?咱們發現應該維護每一個深度的$size$,這樣才能夠直接計算答案。由於題目是在線詢問的,因此每一次合併的時候要開點。

相關文章
相關標籤/搜索