日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[总结] 平衡树总结

發布時間:2025/3/15 编程问答 14 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [总结] 平衡树总结 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. treap

  • 堆的性質
    treap = tree + heap
    也就是 treap 是具有堆性質的平衡二叉樹(BST), 而堆性質的維護就靠一個隨機值和旋轉操作. 可以是小根堆也可以是大根堆.
    用 r 數組(random)保存隨機值, 那么在插入結點時需要維護堆性質(這里是大根堆)
    ch[o][0] 、ch[o][1] 分別表示 結點o 的左兒子和右兒子.
    自己定義宏 :
    #define lc ch[o][0]
    #define rc ch[o][1]
    if(r[ch[o][d]] > r[o]) rotate(o, d^1); // rotate見下方

  • 左旋和右旋 有太多的相似處, 可以用一個帶旋轉方向參數的 rotate 操作來完成
    d = 0 表示左旋, d = 1 表示右旋.
    void rotate(int& o, int d) { int k = ch[o][d^1]; ch[o][d^1] = ch[k][d]; ch[k][d] = o; update(o); update(k); o = k; }

  • 刪除

    • 首先得找到要刪除的結點
    • 如果該結點只有一個左兒子或只有一個右兒子, 直接將它把要刪除的結點取代.

      if(lc * rc == 0) o = lc + rc;
    • 否則就考慮堆性質的維護

      int d2 = tr[t.ch[0]].r < tr[t.ch[1]].r ? 1 : 0;rotate(o, d2); remove(o, x);

  • 查詢
    • 查詢第 k 大
      如果要查詢的結點在當前結點的右兒子上, 那么繼續查詢右兒子的第k-s[lc]-1大.
    • 查詢某結點排名
      如果要查詢的結點在當前結點的右兒子上, 那么排名應該增加s[lc]+1.


2. splay

  • 和 treap 比較起來, splay 多的就是伸展(splay)操作.

  • 關于伸展操作

    • 兩種實現方式 : 自頂向下和自底向上.
      自底向上的 splay 我是參考的 HZWER, 需要使用fa數組記錄當前結點的父結點. 比較方便的是可以直接通過這個fa數組發現從一個結點走到任意已知結點的路徑. 于是就誕生了自底向上的splay. 正因為這種特性, 這種伸展方式可以適應任何題目.

      void rotate(int& px, int& x, int d) {int t = ch[x][d]; ch[x][d] = px; ch[px][d^1] = t; p[x] = p[px]; p[px] = x; p[t] = px; update(px); update(x); px = x; } void splay(int x, int& k) {while(x != k) {int y = p[x], z = p[y];int d = ch[y][0] == x ? 0 : 1;int d2 = ch[z][0] == y ? 0 : 1;if(y != k) rotate(ch[z][d2], x, d^1); else rotate(k, x, d^1);} }
    • 自頂向下的 splay 可以看《訓練指南》, 劉汝佳的代碼. 比較簡潔高效. 我是把指針改成了數組, 不習慣指針的寫法, 主要是不知道怎么調試.

      void splay(int& o, int k) {int d = cmp(o, k);if(d == -1) return;if(d == 1) k -= s[lc] + 1;int p = ch[o][d];int d2 = cmp(p, k);int k2 = (d2 == 0) ? k : k-s[ch[p][0]]-1;if(d2 != -1) {splay(ch[p][d2], k2);if(d == d2) rotate(o, d^1); else rotate(ch[o][d], d);}rotate(o, d^1); }
    • 自頂向下就需要知道你想 splay 的結點的排名. 那么有的題目比如 書架 那個題, 直接告訴你哪個結點編號, 但它是第幾個結點不知道, 這樣就只能用第一種自底向上的splay了.

    • 另外splay可以一次旋轉兩回也可以旋轉一回, 即單旋和雙旋. 單旋的方法比較簡單, 整個過程相當于先找到第 k 個結點, 再遞歸自底向上旋轉到目標結點. 不過在有些情況會退化成一條鏈O(n). 轉兩回的參考《訓練指南》.
      void splay(int& o, int k) {int d = cmp(o, k);if(d == -1) return;if(d == 1) k -= s[lc] + 1;splay(ch[o][d], k); rotate(o, d^1); }

  • 其他操作和 treap 差不多, 但 splay 支持很多區間操作

    • 得到一段區間, 把它移動到一個固定結點.
      都采用自頂向下的splay
      注意這里在首部和末尾都添加一個虛擬結點防止溢出.
    // 移動到根結點右兒子的左兒子 void getInterval(int o, int L, int R) {splay(o, L);splay(rc, R - s[lc] - 1); } // 移動到根結點左兒子的右兒子 void getInterval(int o, int L, int R) {splay(o, L);splay(o, R + 2); }
    • 第二種方法只適用于單旋splay
    • 有了這個獲取區間的題目, 就可以完成很多區間的題目, 比如區間修改、區間翻轉、區間標記之類的.


總結

以上是生活随笔為你收集整理的[总结] 平衡树总结的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。