非旋轉Treap詳解
利用其他人其中考試的時間,終於學完了非旋轉Treap,它與普通Treap的區別就是它不旋轉廢話。前置知識只有BST和可並堆。
BST看這個部落格,解釋的挺清楚的。https://www.cnblogs.com/jiangminghong/p/9999884.html
可並堆就是用很快的時間合併兩個堆。如果裸上一個並查集的話就是nlog2n.這個複雜度我們是不能接受的。正常的可並堆是維護一棵左偏樹,我們用一個引數dis[x]表示從x點出發能夠向右走的最大步數。每次兩個堆合併時,我們就把一個堆扔到另一個堆裡就行了。
在旋轉Treap中,我們用隨機數來保持平衡樹的平衡.非旋轉Treap也是同樣的做法。
1int lson[N];//表示當前點的左兒子 2 int rson[N];//表示當前點的右兒子 3 int key[N];//表示當前點的隨機數(保持平衡樹的平衡) 4 int val[N];//表示當前點的權值 5 int size[N];//表示當前點子樹的大小
這就是我們需要維護的資訊。
看看操作吧
1.Merge【合併】O(logn)
2.Split【拆分】O(logn)
只有兩個
可資瓷使用範圍
1.Insert Newnode+Merge O(logn)
2.Delete Split+Split+Merge O(logn)
3.Find_kth Split+Split O(logn)
4.Query Split+Split O(logn)
5.各種區間操作
1.split
split操作就是以一個點為界限,將平衡樹分裂成兩棵平衡樹,保證左邊平衡樹中任意點權值小於右邊平衡樹中任意點的權值。
以將平衡樹分裂成權值<=val和權值>val兩部分為例:從根節點開始往下查詢,噹噹前點權值<=val時,將當前點及它的右子樹接到分裂後第二棵平衡樹的左子樹上;反之則將當前點及它的左子樹接到分裂後第一棵平衡樹的右子樹上,直到找到葉子節點為止。
void split(int now,int k,int &x,int &y)//now表示現在的位置,k表示要查詢的權值,x,y分別為拆分後兩子樹的根
{
if(!now)
{
x=y=0;
return ;
}
if(val[now]<=k)
x=now,split(rson[now],k,rson[now],y);
else
y=now,split(lson[now],k,x,lson[now]);
push_up(now);
return ;
}
如果想要拆分成前K個點與後n-K個點也是同理的,判斷的條件就是size嘍!
void split(int now,int k,int &x,int &y) { if(!now) { x=y=0; return ; } if(size[lson[now]]<k) x=now,split(rson[now],k-size[lson[now]]-1,rson[now],y); else y=now,split(lson[now],k,x,lson[now]); push_up(now); }
2.merge
merge這個操作就是將兩個堆合併,我們請出可並堆。可並堆的合併方式就是按照隨機數大小來合併。
int merge(int x,int y) { if(!x||!y) return x|y; if(key[x]<key[y]) { rson[x]=merge(rson[x],y); push_up(x); return x; } else { lson[y]=merge(x,lson[y]); push_up(y) return y; } }
有木有一直好奇push_up函式啊,其實和線段樹一樣,維護子樹的一些資訊。
有了這兩個sao操作,是不是就可以在序列上想幹嘛就幹嘛了?
1.Newnode
新建節點,加入平衡樹
int newnode(int x) { size[++tot]=1; val[tot]=x; key[tot]=rand()*rand(); return tot; }
split(root,a,x,y);
root=merge(merge(x,newnode(a)),y);
2.Delete
劃分成3個區間,之後合併
split(root,a,x,z); split(x,a-1,x,y); y=merge(lson[y],rson[y]); root=merge(merge(x,y),z);
3.Query(查詢以x為根排名第K的數)
判斷K和左子樹大小的關係,根據size找到答案。
int kth(int now,int k) { while(1) { if(now==0) break; if(k<=size[lson[now]]) now=lson[now]; else if(k==size[lson[now]]) return now; else k-=size[lson[now]]+1,now=rson[now]; } return 0; }
4.前驅&後繼
split(root,a-1,x,y); printf("%d\n",val[kth(x,size[x])]); root=merge(x,y); //前驅 split(root,a,x,y); printf("%d\n",val[kth(y,1)]); root=merge(x,y); //後繼
LuoguP3369 普通平衡樹
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; #define N 1000100 int tot; int size[N]; int lson[N]; int rson[N]; int key[N]; int val[N]; int n,m; int root; int x,y; void push_up(int x){size[x]=size[lson[x]]+size[rson[x]]+1;return ;} int newnode(int x) { size[++tot]=1; val[tot]=x; key[tot]=rand(); return tot; } int merge(int x,int y) { if(!x||!y) return x+y; if(key[x]<key[y]) { rson[x]=merge(rson[x],y); push_up(x); return x; } else { lson[y]=merge(x,lson[y]); push_up(y); return y; } } void split(int now,int k,int &x,int &y) { if(!now) x=y=0; else { if(val[now]<=k) x=now,split(rson[now],k,rson[now],y); else y=now,split(lson[now],k,x,lson[now]); push_up(now); } return ; } int kth(int now,int k) { while(1) { if(k<=size[lson[now]]) now=lson[now]; else if(k==size[lson[now]]+1) return now; else k-=size[lson[now]]+1,now=rson[now]; } return 0; } int main() { srand(20030305); scanf("%d",&n); int opt,a; while(n--) { scanf("%d%d",&opt,&a); if(opt==1) { split(root,a,x,y); root=merge(merge(x,newnode(a)),y); } if(opt==2) { int z; split(root,a,x,z); split(x,a-1,x,y); y=merge(lson[y],rson[y]); root=merge(merge(x,y),z); } if(opt==3) { split(root,a-1,x,y); printf("%d\n",size[x]+1); root=merge(x,y); } if(opt==4) printf("%d\n",val[kth(root,a)]); if(opt==5) { split(root,a-1,x,y); printf("%d\n",val[kth(x,size[x])]); root=merge(x,y); } if(opt==6) { split(root,a,x,y); printf("%d\n",val[kth(y,1)]); root=merge(x,y); } } return 0; }