1. 程式人生 > >[學習筆記]線段樹騷操作選講

[學習筆記]線段樹騷操作選講

引入

  • 眾所周知,線段樹可以維護序列,進行區間操作
  • 單點加 + 區間求和
  • 區間加 + 區間求和
  • 區間加 + 區間乘 + 區間求和
  • (省略 + +\infty 行)
  • 但有些操作不能像上面三個問題一樣通過簡單的打標記 + 提取區間解決
  • 而需要用到一些 trick

一、勢能線段樹

  • 我們知道,線段樹能夠通過打標記實現區間修改的條件有兩個:
  • (1)能夠快速處理標記對區間詢問結果的影響
  • (2)能夠快速實現標記的合併
  • 但有的區間修改不滿足上面兩個條件
  • 但某些修改存在一些奇妙的性質,使得序列每個元素被修改的次數有一個上限
  • 可以線上段樹每個節點上記錄一個值,表示對應區間內是否每個元素都達到修改次數上限
  • 區間修改時暴力遞迴到葉子節點,如果途中遇到一個節點,這個節點的對應區間內每個元素都達到修改次數上限則在這個節點 return 掉
  • 可以證明覆雜度為 O
    ( n log n ×
    ) O(n\log n\times修改次數上限)
  • 用幾個簡單的栗子說明一下

一、區間開平方,區間求和

  • 一個數 x x 被開平方 O ( log log x ) O(\log\log x) 後會變成 1 1 0 0 ,繼續開根後不變
  • 所以線段樹節點上用一個變數記錄是否區間內全為 1 1 0 0
  • 修改時遞迴到葉子,如果到達了區間內全為 1 1 0 0 的節點則 return 掉
  • 複雜度 O ( n log n log log x ) O(n\log n\log\log x)

二、區間取模,區間求和

  • 一個數 x x p p 取模,如果 x p x\ge p x x 至少變小一半
  • 每個節點維護區間和以及區間最小值
  • 區間對 p p 取模時仍然遞迴到葉子
  • 如果某節點的區間最小值小於 p p 則 return 掉
  • 複雜度 O ( n log n log x ) O(n\log n\log x)

三、區間除(下取整),區間加,區間求和

  • 區間整除 1 1 是無效的,直接跳過
  • 否則整除一個數會使區間內最大值與最小值的差至少減小一半
  • 維護區間最小值和最大值以及區間和,區間加標記
  • 整除時遞迴到葉子
  • 如果最小值和最大值的差為 0 0 ,則該區間內所有數都相等
  • 直接打上標記,注意這時候標記可以處理對區間和的影響
  • 複雜度 O ( n log n log x ) O(n\log n\log x)

二、李超樹 / 李超線段樹 / 超哥線段樹

  • 考慮經典問題
  • 維護一個二維平面
  • 支援橫座標 [ l , r ] [l,r] 範圍內插入一條線段,查詢某個橫座標上的最高點
  • 換成人話,區間對一個等差數列取 max \max ,單點查值
  • 線段樹每個節點維護一個標記(一條線段)
  • 仍然把 [ l , r ] [l,r] 拆成線段樹上不超過 O ( log n ) O(\log n) 個區間
  • 我們要處理的關鍵問題是標記的合併,也就是兩條線段 l 1 l_1 l 2 l_2 放在一起的情況
  • 如果在當前節點對應區間 [ l , r ] [l,r] l 1 l_1 完全在 l 2 l_2 之上,則該區間打上標記線段 l 1 l_1
    在這裡插入圖片描述
  • 如果 l 2 l_2 完全在 l 1 l_1 之上同理
    在這裡插入圖片描述
  • 最重要的情況: l 1 l_1 l 2 l_2 在橫座標 m i d = l + r 2 mid=\lfloor\frac{l+r}2\rfloor 的左邊, l l 的右邊位置相交,在 [ l , r ] [l,r] 的右半段 l 2 l_2 l 1 l_1 之上
    在這裡插入圖片描述
  • 這時候將 l 2 l_2 保留在當前節點上, l 1 l_1 的前半段下傳到左子節點
  • 注意這時候 l 1 l_1 的前半段可能會和左子節點快取的線段相交
  • 所以這時候需要往左子節點遞迴
  • 還有 3 3 種情況和上面差不多
  • 往交點所在的子節點遞迴,另一半區間內在上方的線段保留,在下方的線段下傳
  • 複雜度 O ( n log 2 n ) O(n\log^2n)

三、線段樹維護單調子序列

  • 考慮經典問題
  • 給定一個序列,支援單點修改
  • 詢問給出 [ l , r ] [l,r]
  • 求有多少個 i [ l , r ] i\in[l,r] 滿足 i = l i=l 或者 [ l , i 1 ] [l,i-1] 內任何一個數都不大於第 i i 個數
  • 換句話說,求 [ l , r ] [l,r] 內有多少個位置 i i [ l , r ] [l,r] 內對應的以 i i 為結尾的字首最大值
  • 定義函式 q u e r y ( u , x ) query(u,x)
  • 表示線段樹 u u 節點對應區間內,有多少個數不小於 x x 且是對應區間的字首最大值
  • 線段樹上維護區間最大值
  • 如果 u u 對應的區間最大值小於 x x q u e r y ( u , x ) = 0 query(u,x)=0
  • u u 的左右子樹分別為 l c lc r c rc
  • 如果 l c lc 的最大值小於 x x q u e r y ( u , x ) = q u e r y ( r c , x ) query(u,x)=query(rc,x)