无旋版 $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 ",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 ",rank(x,sze[x])); Root=merge(x,y); } void nxt(int k){ int x,y; split(Root,k,x,y); printf("%d ",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 ",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 ",Root); for(int x,y,i=1;i<=m;i++){ scanf("%d%d",&x,&y); res(x,y); //printf("i=%d ",i); //dfs(Root); } //printf("Root=%d ",Root); dfs(Root); return 0; }