第一框,世界属于fhq-treap
是什么?
你说的对,但是《fhq-treap》是由范浩强自主研发的一款全新树形数据结构。数据结构发生在一个被称作「二叉搜索树」的幻想世界,在这里,被人创造的节点将被授予「随机优先级」,导引期望 \(O(\log n)\) 之力。treap 将扮演一位名为「根据优先级建最小堆,根据权值建搜索树」的神秘角色,在自由的调试中邂逅性格各异、能力独特的同伴们——「分裂、合并、插训排名、查询 k 小值、插入、删除、查询前驱\后继」,和他们一起击败难题,找回出题人的亲人——同时,逐步发掘「大常数」的真相。
怎么做?
$$新建节点与基本定义$$
每一个点有以下几个变量:
struct zxc{int siz;//子树大小int son[2];//son[0]表示左儿子、son[1]表示右儿子int v;//权值int pri;//随机赋予的优先级
}tr[100100];
除此以外,我们还需要 root 表示根(初始为0) 和 tot 表示节点数(初始为0)
treap 的复杂度保障建立于随机优先级,简单来说,当 a 点的优先级小于 b 点的优先级时,a 就是 b 的儿子,不过现在用不着。
新建一个权值为 v 的节点代码如下:
int nw(int v){siz(++tot)=1;v(tot)=v;pri(tot)=rand();return tot;
}
顺便处理一下上传 siz 的函数:
void up(int x){siz(x)=1+siz(ls(x))+siz(rs(x));return;
}
$$合并$$
合并要实现的功能非常好理解,就是把两个节点的子树用神奇强力胶粘到一起。
但是如果你要自己处理这个过程,就会遇到些麻烦,如图所示:
所以我们利用一个递归来帮助实现过程,这样只用把程序丢到一边,让它自己分讨就可以了
int merge(int x,int y){if(!x||!y){//若递归出界了、就直接返回另一个return x+y;//因为有一个是0,所以可以偷一下懒,x+y就是非零的那一个}if(pri(x)<pri(y)){//y要成为x的儿子rs(x)=merge(rs(x),y);//这里的rs和ls都可以,但这是一个习惯,实际上因为需要将merge和split的处理统一化、就一般(至少对我来说)把y放到x的右儿子up(x);//上传sizreturn x;}else{//以下同理ls(y)=merge(x,ls(y));up(y);return y;}
}
$$分裂$$
比较简单,只需要找到需要割的位置,然后割开就可以了(这好像是废话)
分为按权值割和按子树大小割。
按权值割:
void split(int now,int v,int &x,int &y){if(!now){x=y=0;//砍下去!return;}if(v(now)<=v){//若当前的权值小就往右走x=now;//这里和merge同理,实际上x或y都可以split(rs(now),v,rs(now),y);}else{y=now;split(ls(now),v,x,ls(now));}up(now);return;
}
按子树大小割:
void split(int now,int v,int &x,int &y){if(!now){x=y=0;//砍下去!return;}if(siz(ls(now))>=v){//若这个点和左子树的总和太大就往左走y=now;//这里和merge同理,实际上x或y都可以split(ls(now),v,x,ls(now));}else{x=now;split(rs(now),v-siz(ls(now))-1,rs(now),y);}up(now);return;
}