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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

P3369 【模板】普通平衡树 Treap树堆学习笔记

發布時間:2023/12/18 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 P3369 【模板】普通平衡树 Treap树堆学习笔记 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

      • Treap = Tree + Heap (樹堆 = 樹 + 堆)
        • 1. 例題1 洛谷P3369 【模板】普通平衡樹
          • 1. 結構體定義如下
        • 2. 向上更新`以u為根的樹的節點個數`
        • 3. 旋轉操作 route
        • 4. 插入操作 insert
        • 5. 刪除操作 del
        • 6. 查詢排名rand_x
        • 7. 查詢 K 小值 kth
        • 8. 求key的前驅 get_pre
        • 9. 求key的后繼 get_suf
        • 完整代碼
      • 擴展應用
        • 1. 線段樹套平衡樹,求區間前驅后繼排名(就是線段樹的每個節點都是一個平衡樹)
        • 2. 伸展樹, 區間反轉分割
        • 3. 雙端優先隊列的實現 (可以 取最大最小值的堆)
        • 4. 游戲排名系統 (動態數據容器) [原文鏈接https://blog.csdn.net/yang_yulei/article/details/46005845](https://blog.csdn.net/yang_yulei/article/details/46005845)

Treap = Tree + Heap (樹堆 = 樹 + 堆)

大佬的博客https://www.luogu.com.cn/blog/HOJQVFNA/qian-xi-treap-ping-heng-shu

簡介 : 本質上是一種使用 隨機權值維護堆的性質的二叉查找樹, 通過基礎的樹旋轉維持堆的性質,
由隨機權值保證樹高度為 logN

具體做法 : 插入或刪除后, 若不滿足 堆的性質 則旋轉
優點 : 防止 退化成鏈表 簡單好寫, 是最好寫的平衡樹之一, 好理解
缺點 : 常數大


1. 例題1 洛谷P3369 【模板】普通平衡樹

您需要寫一種數據結構(可參考題目標題),來維護一些數,其中需要提供以下操作:

1. 插入 x 數 (插入值)
2. 刪除 x 數(若有多個相同的數,因只刪除一個)
3. 查詢 x 數的排名(排名定義為比當前數小的數的個數 +1 )
4. 查詢排名為 k 的數 (求K小值)
5. 求 x 的前驅 (前驅定義為小于 x,且最大的數)
6. 求 x 的后繼 (后繼定義為大于 x,且最小的數)


1. 結構體定義如下
#define lson(u) (tree[u].chl[0]) #define rson(u) (tree[u].chl[1]) struct Node {int chl[2], //chl[0]是左兒子 , chl[1]是右兒子val, //節點值sz, //以該節點的為根節點個數recy, //該節點重復了幾次rnd; //隨機優先級 } tree[MAXN<<4];

2. 向上更新以u為根的樹的節點個數

  • 當前節點u的sz = 左子樹sz + 右子樹sz + u的重復次數recy(U)
  • inline void push_up(int u) {//更新節點以u為根的樹的節點個數tree[u].sz = tree[lson(u)].sz + tree[rson(u)].sz + tree[u].recy; }

    3. 旋轉操作 route

  • 我們可以把左旋和右旋合并成為一個統一的函數
  • 通過傳入的操作數cmd實現相應的旋轉
  • #define LROUTE 0 #define RROUTE 1 void route(int& u, int cmd) { //cmd=0左旋, cmd=1右旋int tmp = tree[u].chl[cmd^1];tree[u].chl[cmd^1] = tree[tmp].chl[cmd];tree[tmp].chl[cmd] = u;push_up(u), push_up(tmp); //不要忘了更新對應的szu = tmp; }

    4. 插入操作 insert

  • 和普通BST一樣的插入, 并建立隨機權值即可
  • 插入后, 需要向上更新節點的 以該節點的為根節點個數sz
  • void insert(int& u, int key) {if(!u) { //走到葉子就新建節點u = ++tot;tree[u].sz = tree[u].recy = 1;tree[u].val = key;tree[u].rnd = rand(); //隨機權值return ;}tree[u].sz ++;if(key == tree[u].val) { tree[u].recy ++; return ; }int cmd = (key > tree[u].val); //大于則向右插入insert(tree[u].chl[cmd], key);//插入后維護大根堆的性質//失衡的時候, 往與插入相反的方向旋轉if(tree[u].rnd < tree[tree[u].chl[cmd]].rnd)route(u, cmd^1);push_up(u); }

    5. 刪除操作 del

  • 和普通的BST一樣的刪除, 分為 4種情況
  • 沒有這值為 key 的節點, 直接返回
  • 當前根和key不相等, 就去相應的子節點解決
  • 葉子節點, 直接刪除
  • 有左兒子, 沒有右兒子, 右旋并去右兒子解決 (因為右旋后root轉到了rig)
  • 沒有左兒子, 有右兒子, 左旋并去左兒子解決(因為左旋后root轉到了lef)
  • 左右都有, 就把 大的轉上來 并去 另一個兒子 解決
  • 使用 堆刪除的方式, 把key 旋轉到葉子 節點后刪除
  • void del(int& u, int key) {if(!u) return ;if(key < tree[u].val) del(lson(u), key);else if(key > tree[u].val) del(rson(u), key);else { //4種刪除情況if(!lson(u) && !rson(u)) { //葉子節點直接刪除tree[u].sz --,tree[u].recy --;if(tree[u].recy == 0) u = 0;} else if(lson(u) && !rson(u)) { //只有左節點route(u, 1);del(rson(u), key);} else if(!lson(u) && rson(u)) { //只有右節點route(u, 0);del(lson(u), key);} else if(lson(u) && rson(u)) { //左右都有//把大的那個轉上來,并去另一個子樹解決int cmd = (tree[lson(u)].rnd > tree[rson(u)].rnd);route(u, cmd);del(tree[u].chl[cmd], key);}}push_up(u); }

    6. 查詢排名rand_x

  • 還是比較key, 小于往左, 大于往右
  • 往右說明 root的左兒子中的所有節點都 小于key
    所以排名應加上 左兒子的節點的個數根節點的重復次數
  • int rank_x(int u, int key) { //求key的排名if(!u) return 0;if(key == tree[u].val) //返回左子樹個數 + 1return tree[lson(u)].sz + 1;else if(key < tree[u].val) //小于就去左子樹找return rank_x(lson(u), key);else //大于就去右子樹, 類似于權值線段樹return tree[lson(u)].sz +tree[u].recy +rank_x(rson(u), key); }

    7. 查詢 K 小值 kth

  • 如果左子樹節點個數大于等于 k , 說明 k 小值還在左子樹
  • 如果左子樹的節點個數加上根的重復次數仍然小于 k, 說明k小值在右子樹
  • int kth(int u, int k) { //查詢k小值if(!u) return 0;if(tree[lson(u)].sz >= k) return kth(lson(u), k);else if(tree[lson(u)].sz + tree[u].recy < k)return kth(rson(u), k-tree[lson(u)].sz-tree[u].recy);else return tree[u].val; }

    8. 求key的前驅 get_pre

  • 只要根仍然大于等于key, 就向左子樹找
  • 向右走記得取max, 前驅定義為小于key,且最大的數
  • int get_pre(int u, int key) {if(!u) return -INF;if(key <= tree[u].val) return get_pre(lson(u), key);elsereturn max(tree[u].val, get_pre(rson(u), key)); }

    9. 求key的后繼 get_suf

    int get_suf(int u, int key) {if(!u) return INF;if(key >= tree[u].val) return get_suf(rson(u), key);elsereturn min(tree[u].val, get_suf(lson(u), key)); }

    完整代碼

    // #define debug #ifdef debug #include <time.h> #include "win_majiao.h" #endif#include <iostream> #include <algorithm> #include <vector> #include <string.h> #include <map> #include <set> #include <stack> #include <queue> #include <math.h>#define MAXN ((int)1e5+7) #define ll long long int #define INF (0x7f7f7f7f) #define QAQ (0)using namespace std; typedef vector<vector<int> > VVI;#define show(x...) \do { \cout << "[" << #x << " -> "; \err(x); \} while (0)void err() { cout << "]" << endl; } template<typename T, typename... A> void err(T a, A... x) { cout << a << ' '; err(x...); }namespace FastIO{char print_f[105];void read() {}void print() { putchar('\n'); }template <typename T, typename... T2>inline void read(T &x, T2 &... oth) {x = 0;char ch = getchar();ll f = 1;while (!isdigit(ch)) {if (ch == '-') f *= -1; ch = getchar();}while (isdigit(ch)) {x = x * 10 + ch - 48;ch = getchar();}x *= f;read(oth...);}template <typename T, typename... T2>inline void print(T x, T2... oth) {ll p3=-1;if(x<0) putchar('-'), x=-x;do{print_f[++p3] = x%10 + 48;} while(x/=10);while(p3>=0) putchar(print_f[p3--]);putchar(' ');print(oth...);} } // namespace FastIO using FastIO::print; using FastIO::read;int n, m, Q, K, root, tot;#define lson(u) (tree[u].chl[0]) #define rson(u) (tree[u].chl[1]) struct Node {int chl[2], //chl[0]是左兒子 , chl[1]是右兒子val, //節點值sz, //該節點的為根節點個數recy, //該節點重復了幾次rnd; //隨機優先級 } tree[MAXN<<4];void push_up(int u) {//更新節點以u為根的樹的節點個數tree[u].sz = tree[lson(u)].sz + tree[rson(u)].sz + tree[u].recy; }#define LROUTE 0 #define RROUTE 1 void route(int& u, int cmd) { //cmd=0左旋, cmd=1右旋int tmp = tree[u].chl[cmd^1];tree[u].chl[cmd^1] = tree[tmp].chl[cmd];tree[tmp].chl[cmd] = u;push_up(u), push_up(tmp);u = tmp; }void insert(int& u, int key) {if(!u) {u = ++tot;tree[u].sz = tree[u].recy = 1;tree[u].val = key;tree[u].rnd = rand(); //隨機權值return ;}tree[u].sz ++;if(key == tree[u].val) { tree[u].recy ++; return ; }int cmd = (key > tree[u].val); //大于則向右插入insert(tree[u].chl[cmd], key);//插入后維護大根堆的性質//失衡的時候, 往與插入相反的方向旋轉if(tree[u].rnd < tree[tree[u].chl[cmd]].rnd)route(u, cmd^1);push_up(u); }void del(int& u, int key) {if(!u) return ;if(key < tree[u].val) del(lson(u), key);else if(key > tree[u].val) del(rson(u), key);else { //4種刪除情況if(!lson(u) && !rson(u)) { //葉子節點直接刪除tree[u].sz --,tree[u].recy --;if(tree[u].recy == 0) u = 0;} else if(lson(u) && !rson(u)) { //只有左節點route(u, 1);del(rson(u), key);} else if(!lson(u) && rson(u)) { //只有右節點route(u, 0);del(lson(u), key);} else if(lson(u) && rson(u)) { //左右都有//把大的那個轉上來,并去另一個子樹解決int cmd = (tree[lson(u)].rnd > tree[rson(u)].rnd);route(u, cmd);del(tree[u].chl[cmd], key);}}push_up(u); }int rank_x(int u, int key) { //求key的排名if(!u) return 0;if(key == tree[u].val) //返回左子樹個數 + 1return tree[lson(u)].sz + 1;else if(key < tree[u].val) //小于就去左子樹找return rank_x(lson(u), key);else //大于就去右子樹, 類似于權值線段樹return tree[lson(u)].sz +tree[u].recy +rank_x(rson(u), key); }int kth(int u, int k) { //查詢k大值if(!u) return 0;if(tree[lson(u)].sz >= k) return kth(lson(u), k);else if(tree[lson(u)].sz + tree[u].recy < k)return kth(rson(u), k-tree[lson(u)].sz-tree[u].recy);else return tree[u].val; }int get_pre(int u, int key) {if(!u) return -INF;if(key <= tree[u].val) return get_pre(lson(u), key);elsereturn max(tree[u].val, get_pre(rson(u), key)); }int get_suf(int u, int key) {if(!u) return INF;if(key >= tree[u].val) return get_suf(rson(u), key);elsereturn min(tree[u].val, get_suf(lson(u), key)); }signed main() { #ifdef debugfreopen("test.txt", "r", stdin);clock_t stime = clock(); #endifread(n);int cmd, val, ans;for(int i=1; i<=n; i++) {read(cmd, val);switch (cmd) {case 1:insert(root, val);break;case 2:del(root, val);break;case 3:ans = rank_x(root, val);printf("%d\n", ans);break;case 4:ans = kth(root, val);printf("%d\n", ans);break;case 5:ans = get_pre(root, val);printf("%d\n", ans);break;case 6:ans = get_suf(root, val);printf("%d\n", ans);break;default:break;}} #ifdef debugclock_t etime = clock();printf("rum time: %lf 秒\n",(double) (etime-stime)/CLOCKS_PER_SEC); #endif return 0; }

    擴展應用

    1. 線段樹套平衡樹,求區間前驅后繼排名(就是線段樹的每個節點都是一個平衡樹)

    2. 伸展樹, 區間反轉分割

    3. 雙端優先隊列的實現 (可以 取最大最小值的堆)

  • 使用Treap實現取出最大最小值即可
  • 4. 游戲排名系統 (動態數據容器) 原文鏈接https://blog.csdn.net/yang_yulei/article/details/46005845

  • 實現三種操作
    • 上傳一條得分記錄
    • 查看當前排名
    • 返回區間 [L,R] 內的排名記錄
  • 總結

    以上是生活随笔為你收集整理的P3369 【模板】普通平衡树 Treap树堆学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。

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