<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      淺談 FHQ-Treap

      本文同步發表在洛谷博客


      什么是 FHQ-Treap?

      平衡樹上存放兩個信息,權值 \(val\) 以及隨機索引 \(key\)。值滿足二叉搜索樹性質,隨機值索引滿足堆的性質,通過結合二叉搜索樹和二叉堆的性質來使樹平衡。至于這里用的是大根堆還是小根堆,不重要。

      當權值 \(val\) 的數值情況不可控時,如果保證索引 \(key\) 為隨機,樹的期望深度為 \(\log n\)

      通常的平衡樹維護平衡的方法是旋轉,而 FHQ-Treap 則是用分裂和合并來實現的。

      詳解 FHQ-Treap

      FHQ-Treap 存放的節點信息

      首先,有必不可少的權值 \(val\) 和隨即索引 \(key\)。其次為了維護樹的結構,肯定要有左節點編號 \(ls\) 以及右節點編號 \(rs\)。當然,為了求排名之類的東西,我們還會維護一個子樹大小 \(sz\)。由于要維護的信息較多,這里采用結構體進行維護。

      struct FHQ_Trp{int ls,rs,val,key,sz;}tree[N];
      

      當然,你還需要 \(cnt\)\(root\),分別表示當前節點個數(也就是最大的編號)以及當前這棵平衡樹的根節點 \(root\)

      FHQ-Treap 需要用到的操作

      創建新節點

      很簡單。首先將 \(cnt \gets cnt+1\) 然后讓這個節點的編號為 \(cnt\)。給定這個節點的 \(val\) 值,為傳參進來的 \(k\),然后再用 mt19937 隨機生成索引 \(key\),以及要記得初始化子樹大小為 \(1\) 哦。

      mt19937 num(998244353);
      int New_node(int k){
          ++cnt;
          tree[cnt].val=k;
          tree[cnt].key=num();
          tree[cnt].sz=1;
          return cnt;
      }
      

      分裂操作

      分裂,即 Split,是 FHQ-Treap 中必不可少的一環。

      分裂方法有兩種:

      1. 按值分裂:把樹拆成兩棵樹,拆出來的一棵樹的值全部小于等于給定的值,另外一棵樹的值全部大于給定的值。
      2. 按大小分裂:把樹拆成兩棵樹,拆出來的一棵樹的值全部等于給定的大小,剩余部分在另外一顆樹里。

      通常來說,當 FHQ-Treap 作為正常平衡樹使用時,采用按值分裂;維護區間信息時,用按大小分裂。

      這里用按值分裂舉例。其實很簡單的,根據二叉搜索樹的性質,如果當前節點的權值小于等于這個分裂的值,那么左子樹肯定都穩了,可右子樹還確定不了,因此這個時候就往右邊遞歸;同理,如果當前節點的權值大于這個分裂的值,那么右子樹也肯定都穩了,左子樹不確定,這個時候就往左遞歸。更新迭代的時候不要忘了更新分裂出來的兩棵樹的情況,以及它們的根哦。當然,在更新完成之后需要對當前節點進行一次 upd,因為樹的結構會隨著分裂的操作而變化,子樹大小需要重新維護。

      void upd(int u){
          tree[u].sz=tree[tree[u].ls].sz+tree[tree[u].rs].sz+1;return;
      }
      void Split(int u,int k,int &x,int &y){
          if(!u){x=0,y=0;return;}
          else if(tree[u].val<=k){
              x=u;
              Split(tree[u].rs,k,tree[u].rs,y);
          }else{
              y=u;
              Split(tree[u].ls,k,x,tree[u].ls);
          }upd(u);return;
      }
      

      合并操作

      合并操作,很簡單的,就是把兩棵分別以 \(x\)\(y\) 為根的平衡樹合并在一起,并要求合并之后仍然滿足平衡樹的性質。在這里的合并就和之前的分裂不一樣了,分裂的時候重點運用的是二叉搜索樹的性質,而這里需要重點運用二叉堆的性質,去選擇維護的方向。

      int Merge(int x,int y){
          if(!x||!y)return x+y;
          if(tree[x].key>tree[y].key){
              tree[x].rs=Merge(tree[x].rs,y);
              upd(x);return x;
          }else{
              tree[y].ls=Merge(x,tree[y].ls);
              upd(y);return y;
          }
      }
      

      值得注意的是,當你的平衡樹的分裂操作做的是按大小分裂的時候,即你維護區間情況的時候,一定要注意,\(\text{Merge}(x,y)\)\(\text{Merge}(y,x)\) 是在干兩件不同的事情哦!本人被狠狠坑過。

      如何維護 FHQ-Treap

      插入節點

      假設我們要插入的節點的權值為 \(k\)。首先我們要分裂這棵平衡樹,把它分裂成兩個部分,一個部分的權值 \(\le k\),還有一個部分的權值 \(>k\),即執行 \(\text{Split}(root,k,x,y)\) 操作,其中 \(x\)\(y\) 是你設定的兩個變量,用于存儲分裂后的樹的兩個根節點。接著,我們考慮新建一個節點村上這個要插入的值,然后將其和以 \(x\) 為根的樹合并,最后再和以 \(y\) 為根的樹合并。這樣,你就完美地實現了一個插入的過程!

      void Insert(int k){
          int x=0,y=0;
          Split(root,k,x,y);
          root=Merge(Merge(x,New_node(k)),y);
          return;
      }
      

      刪除節點

      同樣假設我們要刪除的節點的權值為 \(k\)。首先,仍然對樹進行分裂,不過這一次我們將把樹分裂成三部分:以 \(x\) 為根節點的部分,所有節點權值 \(< k\);以 \(y\) 為根節點的部分,所有節點權值 \(=k\);以 \(z\) 為根節點的部分,所有節點權值 \(>k\)。這個時候,我們只需要從以 \(y\) 為根節點的樹里面刪掉一個節點,最后把大家全都合并起來就可以了。如何刪掉 \(y\) 中的一個節點?很簡單,由于權值都是 \(k\),沒多大區別,因此直接把根節點刪掉,讓 \(y\) 的左子樹和右子樹合并起來就行了。如果題目特殊要求,說如果存在多個全部刪掉,那么最后就只需要合并 \(x\)\(z\) 了,\(y\) 直接扔掉不管就行。

      void Delete(int k){
          int x=0,y=0,z=0;
          Split(root,k,x,z);
          Split(x,k-1,x,y);
          y=Merge(tree[y].ls,tree[y].rs);
          root=Merge(Merge(x,y),z);
          return;
      }
      

      給定數值查詢排名

      即根據具體值的大小情況查詢排名,如有重復則存在并列情況。假設這個值為 \(k\)

      很簡單啦,同樣對樹進行分裂,不同于以往的 \(\le k\) 以及 \(> k\),這次為了方便求排名我們是按照 \(<k\)\(\ge k\) 去分裂的,然后得到 \(<k\) 的這一部分樹的大小,加上一就是答案。最后記得要把樹合并回來。

      int Get_rank(int k){
          int x=0,y=0,rk=0;
          Split(root,k-1,x,y);
          rk=tree[x].sz+1;
          Merge(x,y);return rk;
      }
      

      給定排名查詢數值

      很簡單,從根節點開始往下搜索,如果左子樹大小 \(+1\) 恰好等于傳參進來的 \(rk\)(也就是排名),那么說明當前 \(u\) 就是答案,直接返回即可;否則根據左子樹的大小是否足夠,判斷現在 \(u\) 是移動向左子樹還是右子樹,同時如果往右子樹移的話要注意將 \(rk\) 的值減少對應數量。如果到最后都還沒找到就返回 \(-1\),但除非這個排名越界了,否則都是會找到的啦!

      int Get_number(int rk){
          int u=root;
          while(u){
              if(tree[tree[u].ls].sz+1==rk)return tree[u].val;
              else if(tree[tree[u].ls].sz>=rk)u=tree[u].ls;
              else rk-=tree[tree[u].ls].sz+1,u=tree[u].rs;
          }return -1;
      }
      

      查詢數值前驅

      數值 \(k\) 的前驅定義為最大的 \(<k\) 的數的具體值,\(k\) 不一定要在樹中出現過。

      依然很簡單,與上面相同的,把樹拆成 \(< k\)\(\ge k\) 的兩部分,然后從 \(<k\) 的那部分樹的根節點找起,只要有右節點,就一直往右邊跑(因為這樣才能得到最大值),直到沒得跑了,直接返回就行了,不過要注意把樹合并回來。

      int Find_pre(int k){
          int x=0,y=0;Split(root,k-1,x,y);
          int u=x;while(tree[u].rs)u=tree[u].rs;
          Merge(x,y);return u;
      }
      

      查詢數值后繼

      數值 \(k\) 的后繼定義為最小的 \(>k\) 的數的具體值,\(k\) 不一定要在樹種出現過。

      和查詢前驅是一樣的,不同的是,這里是把樹拆成 \(\le k\)\(>k\) 兩部分,然后從 \(>k\) 的那部分樹的根節點找起。當然了,這里是一直往左邊跑,因為這樣才能得到最小值嘛!同樣要記得把樹合并回來。

      int Find_nxt(int k){
          int x=0,y=0;Split(root,k,x,y);
          int u=y;while(tree[u].ls)u=tree[u].ls;
          Merge(x,y);return u;
      }
      

      【模版】普通平衡樹

      來看板子題,https://www.luogu.com.cn/problem/P3369

      由于其要求支持插入、刪除、查詢排名、查詢數值、查詢前驅后繼這些操作,正好就是上面所提到的,所以把上面的那些零零散散的代碼拼在一起就過了!

      為了方便我還是放個完整代碼在這里吧。

      #include<bits/stdc++.h>
      #define LL long long
      #define UInt unsigned int
      #define ULL unsigned long long
      #define LD long double
      #define pii pair<int,int>
      #define pLL pair<LL,LL>
      #define pDD pair<LD,LD>
      #define fr first
      #define se second
      #define pb push_back
      using namespace std;
      const int N = 1e5+5;
      mt19937 num(998244353);
      int Q,cnt,root;
      struct FHQ_Trp{int ls,rs,val,key,sz;}tree[N];
      int read(){
          int su=0,pp=1;char ch=getchar();
          while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
          while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
          return su*pp;
      }
      int New_node(int k){
          ++cnt;
          tree[cnt].val=k;
          tree[cnt].key=num();
          tree[cnt].sz=1;
          return cnt;
      }
      void upd(int u){
          tree[u].sz=tree[tree[u].ls].sz+tree[tree[u].rs].sz+1;return;
      }
      void Split(int u,int k,int &x,int &y){
          if(!u){x=0,y=0;return;}
          else if(tree[u].val<=k){
              x=u;
              Split(tree[u].rs,k,tree[u].rs,y);
          }else{
              y=u;
              Split(tree[u].ls,k,x,tree[u].ls);
          }upd(u);return;
      }
      int Merge(int x,int y){
          if(!x||!y)return x+y;
          if(tree[x].key>tree[y].key){
              tree[x].rs=Merge(tree[x].rs,y);
              upd(x);return x;
          }else{
              tree[y].ls=Merge(x,tree[y].ls);
              upd(y);return y;
          }
      }
      void Insert(int k){
          int x=0,y=0;
          Split(root,k,x,y);
          root=Merge(Merge(x,New_node(k)),y);
          return;
      }
      void Delete(int k){
          int x=0,y=0,z=0;
          Split(root,k,x,z);
          Split(x,k-1,x,y);
          y=Merge(tree[y].ls,tree[y].rs);
          root=Merge(Merge(x,y),z);
          return;
      }
      int Get_rank(int k){
          int x=0,y=0,rk=0;
          Split(root,k-1,x,y);
          rk=tree[x].sz+1;
          Merge(x,y);return rk;
      }
      int Get_number(int rk){
          int u=root;
          while(u){
              if(tree[tree[u].ls].sz+1==rk)return tree[u].val;
              else if(tree[tree[u].ls].sz>=rk)u=tree[u].ls;
              else rk-=tree[tree[u].ls].sz+1,u=tree[u].rs;
          }return -1;
      }
      int Find_pre(int k){
          int x=0,y=0;Split(root,k-1,x,y);
          int u=x;while(tree[u].rs)u=tree[u].rs;
          Merge(x,y);return u;
      }
      int Find_nxt(int k){
          int x=0,y=0;Split(root,k,x,y);
          int u=y;while(tree[u].ls)u=tree[u].ls;
          Merge(x,y);return u;
      }
      int main(){
          Q=read();
          while(Q--){
              int opt=read(),x=read();
              if(opt==1)Insert(x);
              else if(opt==2)Delete(x);
              else if(opt==3)cout<<Get_rank(x)<<"\n";
              else if(opt==4)cout<<Get_number(x)<<"\n";
              else if(opt==5)cout<<tree[Find_pre(x)].val<<"\n";
              else cout<<tree[Find_nxt(x)].val<<"\n";
          }
          return 0;
      }
      

      【模版】文藝平衡樹

      就是 https://www.luogu.com.cn/problem/P3391 啦!

      這里維護的是區間,所以在上面分裂那里要按大小分裂。

      問題在于……不管你維護的什么,這里的翻轉操作我也不會弄啊!不過其實沒多難,就是把左右子樹交換 swap 一下。

      可是這么暴力弄都是 \(O(n \times m)\) 的,不超時才怪呢。咋搞?

      還記得線段樹是如何讓區間操作的時間復雜度降下來的?你說對了——懶標記!這里也是一樣的道理!

      在結構體 FHQ_Trp 里多加 \(tag\) 一維,表示這里的標記,每次需要修改,我們就把 \(tag\) 進行反轉。涉及子樹修改的情況下,我們再進行 push_down 下傳標記。

      當然了,我們要進行拆樹(不然你給誰標記呢?),拆成 \([1,l-1]\)\([l,r]\)\([r+1,n]\) 三個部分,然后需要翻轉的當然就是中間 \([l,r]\) 的那一部分啦!最后別忘了并回去。

      #include<bits/stdc++.h>
      #define LL long long
      #define UInt unsigned int
      #define ULL unsigned long long
      #define LD long double
      #define pii pair<int,int>
      #define pLL pair<LL,LL>
      #define pDD pair<LD,LD>
      #define fr first
      #define se second
      #define pb push_back
      using namespace std;
      const int N = 1e5+5;
      mt19937 num(998244353);
      int n,m,cnt,root;
      struct FHQ_Trp{int ls,rs,val,key,sz;bool tag;}tree[N];
      int read(){
          int su=0,pp=1;char ch=getchar();
          while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
          while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
          return su*pp;
      }
      int New_node(int k){
          ++cnt;
          tree[cnt].val=k;
          tree[cnt].key=num();
          tree[cnt].sz=1;
          return cnt;
      }
      void push_down(int u){
          if(!tree[u].tag)return;
          swap(tree[u].ls,tree[u].rs);
          tree[tree[u].ls].tag^=1;
          tree[tree[u].rs].tag^=1;
          tree[u].tag=0;return;
      }
      void upd(int u){
          tree[u].sz=tree[tree[u].ls].sz+tree[tree[u].rs].sz+1;return;
      }
      void Split(int u,int k,int &x,int &y){
          if(!u){x=0,y=0;return;}
          push_down(u);
          if(tree[tree[u].ls].sz+1<=k){
              x=u;
              Split(tree[u].rs,k-tree[tree[u].ls].sz-1,tree[u].rs,y);
          }else{
              y=u;
              Split(tree[u].ls,k,x,tree[u].ls);
          }upd(u);return;
      }
      int Merge(int x,int y){
          if(!x||!y)return x+y;
          if(tree[x].key>tree[y].key){
              push_down(x);
              tree[x].rs=Merge(tree[x].rs,y);
              upd(x);return x;
          }else{
              push_down(y);
              tree[y].ls=Merge(x,tree[y].ls);
              upd(y);return y;
          }
      }
      void Insert(int k){
          int x=0,y=0;
          Split(root,k,x,y);
          root=Merge(Merge(x,New_node(k)),y);
          return;
      }
      void Delete(int k){
          int x=0,y=0,z=0;
          Split(root,k,x,z);
          Split(x,k-1,x,y);
          y=Merge(tree[y].ls,tree[y].rs);
          root=Merge(Merge(x,y),z);
          return;
      }
      void Sol(int l,int r){
          int x=0,y=0,z=0;
          Split(root,r,x,z);
          Split(x,l-1,x,y);
          tree[y].tag^=1;
          root=Merge(Merge(x,y),z);
          return;
      }
      void DFS(int u){
          if(!u)return;
          push_down(u);
          DFS(tree[u].ls);
          cout<<tree[u].val<<" ";
          DFS(tree[u].rs);
          return;
      }
      int main(){
          n=read(),m=read();
          for(int i=1;i<=n;i++)Insert(i);
          for(int i=1;i<=m;i++){
              int l=read(),r=read();
              Sol(l,r);
          }DFS(root);cout<<"\n";
          return 0;
      }
      
      posted @ 2025-10-29 17:44  嘎嘎喵  閱讀(14)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 爱性久久久久久久久| 国产熟女一区二区三区四区| 亚洲最大成人免费av| 一区二区三区无码视频免费福利 | 国产一区二区av天堂热| 人妻在线中文字幕| 亚洲av鲁丝一区二区三区黄| 精品国产午夜肉伦伦影院| 亚洲日韩久热中文字幕| 日韩一区二区在线看精品| 凸凹人妻人人澡人人添| 亚洲色偷偷色噜噜狠狠99| 美女一级毛片无遮挡内谢| 爆乳日韩尤物无码一区| 国产日韩综合av在线| 国内精品无码一区二区三区| 日韩精品一区二区三免费| 欧美xxxx做受欧美| 国产精品69人妻我爱绿帽子| 四虎永久精品免费视频| 在线观看中文字幕国产码| 挺进粗大尤物人妻中文字幕| 韩国 日本 亚洲 国产 不卡| 成人又黄又爽又色的视频| 丁香婷婷色综合激情五月| 人妻丝袜AV中文系列先锋影音| 宜兴市| 精品人妻系列无码天堂| 亚洲人成网站观看在线观看 | 亚洲国产精品一区二区第一页| 极品粉嫩小泬无遮挡20p| 天天爱天天做天天爽夜夜揉 | 国产精品日韩中文字幕熟女| 亚洲精品国产一二三区| 国产av丝袜熟女一二三| 久久av色欲av久久蜜桃网| 国产成人综合亚洲欧美日韩| 亚洲综合在线日韩av| 资溪县| 国产欧洲欧洲久美女久久| 日韩中文字幕国产精品|