無旋版 $Treap$。
只需要兩個操作即可達到 $splay$ 的所有功能
1、$split$
它的主要思想就是把一個 $Treap$ 分成兩個。
$split$ 操作有兩種類型,一種是按照權值分配,一種是按前 k 個分配。
第一種就是把所有小於 k 的權值的節點分到一棵樹中,第二種是把前 k 個分到一個樹里。
權值版:
1 void split(int o,int k,int &x,int &y){ //這里的x,y分別是將以o為根的樹切開后第一個新子樹的根和第二個新子樹的根 2 if(!o) x=y=0; 3 else { 4 if(val[o]<=k) 5 x=o,split(ch[o][1],k,ch[o][1],y); 6 else 7 y=o,split(ch[o][0],k,x,ch[o][0]); 8 pushup(o); 9 } 10 }
對於我們遍歷到每一個點,假如它的權值小於k,那么它的所有左子樹,都要分到左邊的樹里,然后遍歷它的右兒子。假如大於k,把它的所有右子樹分到右邊的樹里,遍歷左兒子。
因為它的最多操作次數就是一直分到底,效率就是 $O(logn)$。
對於前k個版的,就是像找第k大的感覺。每次減掉sze
void split(int now,int k,int &x,int &y){ if (!now) x=y=0; else{ if (k<=siz[ch[now][0]]) y=now,split(ch[now][0],k,x,ch[now][0]); else x=now,split(ch[now][1],k-sze[ch[now][0]]-1,ch[now][1],y); pushup(now); } }
2、$merge$
這個就是把兩個 $Treap$ 合成一個,保證第一個的權值小於第二個。
因為第一個 $Treap$ 的權值都比較小,我們比較一下它的 $prio$ (優先級),假如第一個的 $prio$ 小,我們就可以直接保留它的所有左子樹,接着把第一個 $Treap$ 變成它的右兒子。反之,我們可以保留第二棵的所有右子樹,指針指向左兒子。
你可以把這個過程形象的理解為在第一個 $ Treap$ 的右子樹上插入第二個樹,也可以理解為在第二個樹的左子樹上插入第一棵樹。因為第一棵樹都滿足小於第二個樹,所以就變成了比較 $prio$ 來確定樹的形態。
也就是說,我們其實是遍歷了第一個$Treap$ 的根->最大節點,第二個$Treap$的根->最小節點,也就是 $O(logn)$
int merge(int x,int y){ if(!x or !y) return x+y; if(prio[x]<prio[y]){ ch[x][1]=merge(ch[x][1],y); pushup(x); return x; } else{ ch[y][0]=merge(x,ch[y][0]); pushup(y); return y; } }
下面我們就可以通過這兩個基本的東西實現各種各樣的操作了。
3、insert
插入一個權值為 $k$ 的點,把樹按照 $k$ 的權值 $split$ 成兩個,再 $merge$ 回去。
4、remove
刪除權值為 $k$ 的點,把樹按照 $k$ 分成兩個$a,b$ 再把$a$ 按照 $k-1$ 分成$c,d$。把$d$ 的兩個兒子 $merge$起來,再 $merge(merge(c,d),b)$
void remove(int k){ int x,y,z; split(Root,k,x,y); split(x,k-1,x,z); z=merge(ch[z][0],ch[z][1]); Root=merge(x,merge(z,y)); }
其它見代碼
// 普通平衡樹 fhq_Treap // By YoungNeal #include<cstdio> #include<cstdlib> #define N 100005 #define inf 0x3f3f3f3f int Root; int n,opt,x,tot; int val[N],prio[N]; int sze[N],ch[N][2]; void pushup(int o){ sze[o]=sze[ch[o][0]]+sze[ch[o][1]]+1; } void split(int o,int k,int &x,int &y){ if(!o) x=y=0; else { if(val[o]<=k) x=o,split(ch[o][1],k,ch[o][1],y); else y=o,split(ch[o][0],k,x,ch[o][0]); pushup(o); } } int merge(int x,int y){ if(!x or !y) return x+y; if(prio[x]<prio[y]){ ch[x][1]=merge(ch[x][1],y); pushup(x); return x; } else{ ch[y][0]=merge(x,ch[y][0]); pushup(y); return y; } } int newnode(int v){ sze[++tot]=1; val[tot]=v; prio[tot]=rand(); return tot; } void insert(int k){ int x,y; split(Root,k,x,y); Root=merge(merge(x,newnode(k)),y); } void remove(int k){ int x,y,z; split(Root,k,x,y); split(x,k-1,x,z); z=merge(ch[z][0],ch[z][1]); Root=merge(x,merge(z,y)); } void kthrank(int k){ int x,y; split(Root,k-1,x,y); printf("%d\n",sze[x]+1); Root=merge(x,y); } int rank(int o,int k){ if(sze[ch[o][0]]==k-1) return val[o]; if(sze[ch[o][0]]>=k) return rank(ch[o][0],k); return rank(ch[o][1],k-sze[ch[o][0]]-1); } void prev(int k){ int x,y; split(Root,k-1,x,y); printf("%d\n",rank(x,sze[x])); Root=merge(x,y); } void nxt(int k){ int x,y; split(Root,k,x,y); printf("%d\n",rank(y,1)); Root=merge(x,y); } signed main(){ scanf("%d",&n); while(n--){ scanf("%d%d",&opt,&x); if(opt==1) insert(x); if(opt==2) remove(x); if(opt==3) kthrank(x); if(opt==4) printf("%d\n",rank(Root,x)); if(opt==5) prev(x); if(opt==6) nxt(x); } return 0; }
5、區間操作
對於翻轉區間 $[l,r]$,我們可以先把區間 $[1,l-1]$ $split$ 出來,再把 $[l,r]$ $split$ 出來就行了。注意 $lazy$ 標記及時清除。
// 文藝平衡樹 fhp_Treap // By YoungNeal #include<ctime> #include<cstdio> #include<cstdlib> #define N 100005 int Root; int lazy[N]; int n,m,cnt; int val[N],sze[N]; int ch[N][2],prio[N]; void pushup(int o){ sze[o]=sze[ch[o][0]]+sze[ch[o][1]]+1; } void pushdown(int o){ if(!lazy[o] or !o) return; ch[o][0]^=ch[o][1]^=ch[o][0]^=ch[o][1]; lazy[ch[o][0]]^=1; lazy[ch[o][1]]^=1; lazy[o]=0; } void split(int o,int k,int &x,int &y){ if(!o) x=y=0; else{ pushdown(o); if(k>sze[ch[o][0]]) x=o,split(ch[o][1],k-sze[ch[o][0]]-1,ch[o][1],y); else y=o,split(ch[o][0],k,x,ch[o][0]); pushup(o); } } int merge(int x,int y){ if(!x or !y) return x+y; pushdown(x); pushdown(y); if(prio[x]<prio[y]){ ch[x][1]=merge(ch[x][1],y); pushup(x); return x; } else{ ch[y][0]=merge(x,ch[y][0]); pushup(y); return y; } } int newnode(int v){ val[++cnt]=v; sze[cnt]=1; prio[cnt]=rand(); return cnt; } void res(int l,int r){ int a,b,c,d; split(Root,r,a,b); split(a,l-1,c,d); lazy[d]^=1; Root=merge(merge(c,d),b); } void dfs(int now){ if(!now) return; pushdown(now); dfs(ch[now][0]); printf("%d ",val[now]); dfs(ch[now][1]); } signed main(){ srand(time(0)); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) Root=merge(Root,newnode(i)); //printf("Root=%d\n",Root); for(int x,y,i=1;i<=m;i++){ scanf("%d%d",&x,&y); res(x,y); //printf("i=%d\n",i); //dfs(Root); } //printf("Root=%d\n",Root); dfs(Root); return 0; }