口胡
其实就是传说中的无旋treap。鉴于我总是写不出无旋treap,但是无旋treap又意外的好用,而且之前这个无旋treap板子是在远航大佬的博客上学的,但是远航貌似现在没时间维护博客,所以我就发一篇方便以后自己翻翻。其实远航大佬还是写的很好的,如果后续有维护的话建议看远航的文章入门。。
无旋treap好打,同样可以支持按权值维护或者维护序列,核心函数一般顶多四个,所以码量相比于splay确实会少很多,在一般需要支持一个比较简单的操作但set却不支持时,是一个很好的替代品。不信看下面普通平衡树的代码。
本质就是维护一个以rand为权的小根堆,由于rand是我们自己随机出来的,所以树高期望是 O ( log n ) O(\log n) O(logn)的。
合并
无论是维护权值还是序列,都通用一个合并函数,而这个函数其实是和可并堆相似的,由于x完全y左边,所以才可以做到 O ( log n ) O(\log n) O(logn)的合并。我直到今年暑假还一直误以为它可以 O ( log n ) O(\log n) O(logn)合并任意两棵按权值排的无旋treap。
int merge(int x,int y)
{
if(!x||!y) return x+y;
if(rnd[x]<rnd[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;
}
}
分裂
维护权值的分裂长这样,其中k的含义是把小于等于k的都放进x子树中,大于的则在y子树中。
void split(int now,int k,int &x,int &y)
{
if(!now) x=y=0;
else
{
if(val[now]<=k) x=now,split(ch[now][1],k,ch[now][1],y);
else y=now,split(ch[now][0],k,x,ch[now][0]);
pushup(now);
}
}
而维护序列的长这样,k的含义是把前k个放进x子树中,其他放进y子树中。
void split(int now,int k,int &x,int &y)
{
if(!now) x=y=0;
else
{
if(k<=s[ch[now][0]]) y=now,split(ch[now][0],k,x,ch[now][0]);
else x=now,split(ch[now][1],k-s[ch[now][0]]-1,ch[now][1],y);
pushup(now);
}
}
第k小
在找前驱/后继时,为了方便实现代码我们会需要这么一个函数。本质和splay的kth一致,就不再赘述。
int kth(int now,int k)
{
while(1)
{
if(k<=s[ch[now][0]]) now=ch[now][0];
else
{
if(k==s[ch[now][0]]+1) return now;
else k-=s[ch[now][0]]+1,now=ch[now][1];
}
}
}
其他操作
其他的很多操作可以自己yy的,通常的套路就是先split成两棵子树,然后再做操作,最后合并回来即可。
比如维护权值的时候插入一个x,那么就是按x分裂一下,在新增一个节点,再依次合并。
Code
普通平衡树的代码。
#include <cstdlib>
#include <cstdio>
#define rg register
#define pushup(x) s[x]=s[ch[x][0]]+s[ch[x][1]]+1
using namespace std;
typedef long long ll;
const int maxn=100010;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
x=0;int f=0;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
int m,sz,rt,op,a,x,y,z,val[maxn],rnd[maxn],s[maxn],ch[maxn][2];
int new_node(int x)
{
s[++sz]=1;val[sz]=x;rnd[sz]=rand();
return sz;
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
if(rnd[x]<rnd[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;
}
}
void split(int now,int k,int &x,int &y)
{
if(!now) x=y=0;
else
{
if(val[now]<=k) x=now,split(ch[now][1],k,ch[now][1],y);
else y=now,split(ch[now][0],k,x,ch[now][0]);
pushup(now);
}
}
int kth(int now,int k)
{
while(1)
{
if(k<=s[ch[now][0]]) now=ch[now][0];
else
{
if(k==s[ch[now][0]]+1) return now;
else k-=s[ch[now][0]]+1,now=ch[now][1];
}
}
}
int main()
{
srand(19260817);
read(m);
while(m--)
{
read(op);read(a);
if(op==1){split(rt,a,x,y);rt=merge(merge(x,new_node(a)),y);}
if(op==2)
{
split(rt,a,x,z);split(x,a-1,x,y);
y=merge(ch[y][0],ch[y][1]);
rt=merge(merge(x,y),z);
}
if(op==3){split(rt,a-1,x,y);printf("%d\n",s[x]+1);rt=merge(x,y);}
if(op==4) printf("%d\n",val[kth(rt,a)]);
if(op==5)
{
split(rt,a-1,x,y);
printf("%d\n",val[kth(x,s[x])]);
rt=merge(x,y);
}
if(op==6)
{
split(rt,a,x,y);
printf("%d\n",val[kth(y,1)]);
rt=merge(x,y);
}
}
return 0;
}