1. 程式人生 > 實用技巧 >樹套樹學習筆記

樹套樹學習筆記

1.例題入門

例題:洛谷傳送門P3380 【模板】二逼平衡樹(樹套樹)

題目大意:維護一個有序數列,其中需要提供以下操作:

  1. 查詢k在區間內的排名

  2. 查詢區間內排名為k的值

  3. 修改某一位值上的數值

  4. 查詢k在區間內的前驅(前驅定義為嚴格小於x,且最大的數,若不存在輸出-2147483647)

  5. 查詢k在區間內的後繼(後繼定義為嚴格大於x,且最小的數,若不存在輸出2147483647)

  6. 時空限制:2s,128M,資料規模:n,m<5*10^4,任意時刻序列元素滿足[0,10^8]

2.思路分析

首先,如果只看發現這道題很像平衡樹(沒學過的可以先看看這篇部落格吖~

https://www.cnblogs.com/Roysblog/p/13499395.html),但是這道題做了一些變化,詢問給定區間內排名為k的樹,於是

便想到了維護任意區間的資料結構:線段樹。這道題的具體思路是:維護一棵線段樹,每個結點用兩個引數l,r維護著一段區間,每個結點都是一個Splay旋轉平衡樹,然後,對於區間詢問,我們遞迴到每個結點

再利用Splay進行計算。

於是,這道題的資料結構便出來了:線段樹套平衡樹

3.程式碼:(內含英文註釋&&中文註釋)

#include<iostream>
#include<cstdio>
#include<cstring>
#include
<cmath> #include<algorithm> #define ls(x) t[x].son[0] #define rs(x) t[x].son[1] #define INF 2147483647 using namespace std; const int N =5e4 + 5; struct Splay_{ int size;int tot;int val;int son[2];int fa; Splay_(){size=0;tot=0;} }; struct Seg_tree{ int l;int r;int root; }; Splay_ t[
50*N]; Seg_tree tr[(N<<2)]; int a[N],n,m,cnt=0; //**********************Splay平衡樹**************************************** inline void push_up(int x) {t[x].size=t[ls(x)].size+t[rs(x)].size+t[x].tot;} inline void rotate(int x) { int f=t[x].fa;int g_f=t[f].fa;int locate=(t[f].son[1]==x); t[g_f].son[(t[g_f].son[1]==f)]=x;t[x].fa=g_f; t[t[x].son[locate^1]].fa=f;t[f].son[locate]=t[x].son[locate^1]; t[x].son[locate^1]=f;t[f].fa=x; push_up(f);push_up(x); } inline void Splay(int x,int goal,int Node) { while(t[x].fa!=goal) { int f=t[x].fa;int g_f=t[f].fa; if(g_f!=goal) (t[g_f].son[1]==f)^(t[f].son[1]==x) ? rotate(f) :rotate(x); rotate(x); } if(!goal) tr[Node].root=x; } inline int init(int v,int fa) { t[++cnt].val=v;t[cnt].fa=fa;t[cnt].tot=1; push_up(cnt);return cnt; } inline void insert(int x,int Node) { int u=tr[Node].root;int fa=0; if(!u){u=init(x,0);tr[Node].root=u;return;} while(u&&(t[u].val!=x)){fa=u;u=t[u].son[t[u].val<x];} if(u&&(x== t[u].val)) t[u].tot++; else{u=init(x,fa);if(fa)t[fa].son[t[fa].val<x]=u;} Splay(u,0,Node); } inline void find(int x,int Node) { int u=tr[Node].root; if(!u) return ; while(t[u].son[t[u].val<x] && t[u].val!=x) u=t[u].son[t[u].val<x]; Splay(u,0,Node); } inline int Next(int x,int locate,int Node) { find(x,Node); int u=tr[Node].root; if((locate&&t[u].val<x)||(!locate&&t[u].val>x)) return u; u=t[u].son[locate^1]; while(t[u].son[locate]) u=t[u].son[locate]; return u; } inline void Del(int x,int Node) { int pre=Next(x,1,Node);int nxt=Next(x, 0, Node); Splay(nxt, 0, Node); Splay(pre, nxt, Node); int wh = t[pre].son[1]; if(t[wh].tot > 1) -- t[wh].tot, Splay(wh, 0, Node); else t[pre].son[1] = 0; push_up(pre); } //************************************************************************** //***************Segment_tree*********************************************** inline void build(int Node,int l,int r ) { insert(INF,Node);insert(-INF,Node); if(l==r) return ; int mid=((l+r)>>1); build((Node<<1),l,mid);build((Node<<1)+1,mid+1,r); } inline void Seg_insert(int Node,int l,int r,int k,int val) { int mid=((l+r)>>1); insert(val,Node); if(l==r) return ; if(mid>=k) Seg_insert((Node<<1),l,mid,k,val); else Seg_insert((Node<<1)+1,mid+1,r,k,val); } inline int Seg_rank(int Node,int l,int r,int k,int x,int y) { if(l>y||r<x) return 0; if(l>=x&&r<=y) { find(k,Node);int u=tr[Node].root; if(t[u].val>=k) return t[ls(u)].size-1; else return t[ls(u)].size+t[u].tot-1; } int mid=((l+r)>>1); return Seg_rank((Node<<1),l,mid,k,x,y)+Seg_rank((Node<<1)+1,mid+1,r,k,x,y); } inline void Seg_update(int Node,int l,int r,int wh,int val) { Del(a[wh],Node);insert(val,Node); if((l==r)&&(l==wh)){a[wh]=val;return ;} int mid=((l+r)>>1); if(mid>=wh) Seg_update((Node<<1),l,mid,wh,val); else Seg_update((Node<<1)+1,mid+1,r,wh,val); } inline int Seg_Pre(int Node,int l,int r,int x,int y,int k)//線段樹求前驅 { if(l>y || r<x) return -INF; if(l>=x&&r<=y) return t[Next(k,1,Node)].val; int mid=((l+r)>>1); return max(Seg_Pre((Node<<1),l,mid,x,y,k),Seg_Pre((Node<<1)+1,mid+1,r,x,y,k)); } inline int Seg_Next(int Node,int l,int r,int x,int y,int k) //線段樹裡尋找後繼 { if(l>y||r<x) return INF; if(l>=x&&r<=y) return t[Next(k,0,Node)].val; int mid=((l+r)>>1); return min(Seg_Next((Node<<1),l,mid,x,y,k),Seg_Next((Node<<1)+1,mid+1,r,x,y,k)); } inline int Seg_NoK(int x,int y,int k)//求區間內第k大的數 { int l=0,r=1e8,mid=((l+r)>>1),check,ans=0; while(l<=r) //二分查詢編號 { mid=((l+r)>>1); check=Seg_rank(1,1,n,mid,x,y)+1; if(check>k) r=mid-1; else {l=mid+1;ans=mid;} } return ans; } //**************Quick_Read************************************************** inline int Read() { int num=0,k=1; char c=getchar(); while(c!='-'&&(c>'9'||c<'0')) c=getchar(); if(c=='-'){k=-1;c=getchar();} while(c>='0'&&c<='9'){num=(num<<1)+(num<<3)+(c^48);c=getchar();} return num*k; } //*************************main*************************** int main() { n=Read();m=Read(); build(1,1,n);int op,l,r,k; for(int i=1;i<=n;i++){a[i]=Read();Seg_insert(1,1,n,i,a[i]);} for(int i=1;i<=m;i++) { op=Read();l=Read();r=Read(); switch(op) { case 1:{k=Read();printf("%d\n",Seg_rank(1,1,n,k,l,r)+1);break;} case 2:{k=Read();printf("%d\n",Seg_NoK(l,r,k));break;} case 3:{Seg_update(1,1,n,l,r);break;} case 4:{k=Read();printf("%d\n",Seg_Pre(1,1,n,l,r,k));break;} case 5:{k=Read();printf("%d\n",Seg_Next(1,1,n,l,r,k));break;} } } return 0; }

洛谷上也有題解,蒟蒻題解大佬指正~(手動ORZ)