因为splay不兹磁可持久化而且我天天写挂TAT,最近学习了一发treap
treap=tree+heap
他同时有二叉搜索树和堆的性质,每个节点有两个权值,val和fix,val是我们要维护的权值,fix是我们给这个点随机的一个权值
这棵树对于val满足中序遍历有序,对于fix满足堆的性质
treap的复杂度由这个随机的fix值来保证,因为fix值是随机的,所以treap的树高期望是log层的(大概是由随机的性质,用调和级数来证的,具体证明我不大会
treap有2种,旋转的和不旋转的
旋转的treap,感觉和splay差不多,大概是按照val值插入一个点后,把他往上旋直到满足堆的性质,旋转和splay的旋转感觉是一样的,但这东西不兹磁区间操作,而且因为他要旋转,所以他同样不兹磁可持久化
可旋转的treap有2个性质:
每次插入,期望旋转次数O(1)
每次插入的点,旋转后他的子树期望大小O(logn)
(dwj给我们证过但我果然听完还是不会证)
非旋转的treap,其主要依赖split和merge这两个函数,用这两个函数可以轻松的提取区间,所以做一些区间的操作很轻松,而且因为他不需要旋转,不需要维护fa,他是兹磁可持久化的
关于建树,用笛卡尔树的建法,维护最右边的一条链,新加入一个点就和链尾比较fix值,能加就加,不能加就删链尾,然后把最后一个删掉的链尾作为他的左儿子,自己加入这条链,每个元素删一次所以这个建树是O(n)的,初始化和替罪羊树同构都会用到这个build函数
split有两种,按权值和按排名分裂,大概就是看这个点是属于分裂后的左树还是右树,属于左边就把他和他的左孩子给左边,递归他的右孩子,右边同理
例子(按排名拆)
void split(int x,int &f1,int &f2,int k)
{
f1=f2=0;
if(!x) return;
pushdown(x);
if(siz[son[x][0]]+1<=k)
{
f1=x; int y=son[x][1]; son[x][1]=0;
split(y,son[x][1],f2,k-(siz[son[x][0]]+1));
}
else
{
f2=x; int y=son[x][0]; son[x][0]=0;
split(y,f1,son[x][0],k);
}
pushup(x);
}
merge和可并堆的合并感觉差不多,不能交换孩子就判一下
栗子
int merge(int x,int y)
{
if(!x||!y) return x|y;
if(fix[x]<fix[y])
{
pushdown(x);
son[x][1]=merge(son[x][1],y);
pushup(x);
return x;
}
else
{
pushdown(y);
son[y][0]=merge(x,son[y][0]);
pushup(y);
return y;
}
}
可持久化的话和线段树的可持久化也差不多的样子就不讲了(其实是因为我还没写过…..)
关于随机函数rand(),用系统函数可能会有重复,所以一般这样写
int rand_()
{
static int seed=2333333;
return seed = (ll)seed*48271ll%INT_MAX;
}
随机种子seed是随便定的,乘那个48271是因为48271是模数
231−1
2
31
−
1
的其中一个不大不小的原根,能把
1
1
~所有数取一遍
(其实不用48271也行,这个模数的原根我打了个表发现特别多= =,随便再取一个不要太大也不要太小的原根应该也是可以的)