简介
无旋树堆(一般统称 \(\text{FHQ-Treap}\)),是一种平衡树。可以用很少的代码达到很优秀的复杂度。
前置知识:
- 二叉搜索树 \(\text{BST}\)
- \(\text{Treap}\) 基本知识
普通平衡树例题引入
P6136 【模板】普通平衡树(数据加强版)
您需要写一种数据结构,来维护一些整数,其中需要提供以下操作:
- 插入一个整数 \(x\)。
- 删除一个整数 \(x\)(若有多个相同的数,只删除一个)。
- 查询整数 \(x\) 的排名(排名定义为比当前数小的数的个数 \(+1\))。
- 查询排名为 \(x\) 的数(如果不存在,则认为是排名小于 \(x\) 的最大数。保证 \(x\) 不会超过当前数据结构中数的总数)。
- 求 \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)。
- 求 \(x\) 的后继(后继定义为大于 \(x\),且最小的数)。
本题强制在线,保证所有操作合法(操作 \(2\) 保证存在至少一个 \(x\),操作 \(4,5,6\) 保证存在答案)。
对于 \(100\%\) 的数据,\(1\leq n\leq 10^5\),\(1\leq m\leq 10^6\),\(0\leq a_i,x\lt 2^{30}\)。
准备姿势
const int SIZE = 2e6+5; // 数组大小int son[SIZE][2]; // 平衡树数组int val[SIZE],rk[SIZE],siz[SIZE]; // val是权值,rk是随机权值,siz是子树大小int root,points; // root是树根,points是点数(最后一个节点的编号)mt19937 randomer(time(0)); // 随机生成器#define ls(i) (son[(i)][0]) // 左子树#define rs(i) (son[(i)][1]) // 右子树void pushup(int i){ // 上推信息,这里是子树大小siz[i]=siz[ls(i)]+siz[rs(i)]+1;}int newnode(int v){ // 新建节点val[++points]=v; // 权值赋值rk[points]=randomer(); // 随机生成随机权值siz[points]=1; // 散点子树大小为1return points; // 返回节点编号}
FHQ-Treap 基本操作——分裂(split)
这里的 \(\text{split}\) 是按大小分裂,按权值分裂将会在【文艺平衡树】中讲。
\(\operatorname{split}(p,v,l,r)\) 定义为,将根为 \(p\) 的树,分裂成两个子树 \(l,r\) 使得 \(l\) 的所有点权小于等于 \(v\),\(r\) 中的所有点权大于 \(v\)。
实现也是非常的暴力,由于BST中,左子树小于根,右子树大于根,于是直接判断,如果 \(p\) 的全职 \(\le v\),我们就往右子树递归看看有没有机会(左子树一定满足),否则就往左子树递归。
代码:
void split(int p,int v,int &left,int &right){if(!p){ // 节点不存在的情况left=right=0;return;}if(val[p]<=v){left=p;split(rs(p),v,rs(left),right);}else{right=p;split(ls(p),v,left,ls(right));}pushup(p);}
由于最多一条链从根搜到叶子,所以时间复杂度是 \(O(\log n)\) 的。
FHQ-Treap 基本操作——合并(merge)
\(\operatorname{merge}(l,r)\) 指,将 \(l,r\) 两棵树合并,然后返回新的树的树根。
如果打过线段树合并,那么打这一部分的内容会感觉很亲切。
在 \(\text{Treap}\) 中,合并方向取决于随机权值,\(\text{FHQ-Treap}\) 也不例外。
如果 \(l\) 的权值大于 \(r\) 的,那么保留 \(l\) 的左子树,右子树改为 \(\operatorname{merge}(r,\operatorname{rightSon}(l))\)。
如果 \(r\) 的权值大于 \(l\) 的,那么保留 \(r\) 的左子树,右子树改为 \(\operatorname{merge}(l,\operatorname{rightSon}(r))\)。
代码:
int merge(int left,int right){if(!left||!right){ // 节点不存在的情况if(left)return left;else if(right)return right;else return 0;}if(rk[left]<rk[right]){ls(right)=merge(left,ls(right));pushup(right);return right;}else{rs(left)=merge(rs(left),right);pushup(left);return left;}}
时间复杂度 \(O(\log n)\)。
FHQ-Treap 其他操作——insert
\(\operatorname{insert}(v)\),在平衡树中插入一个数 \(v\)。
我们可以将平衡树分裂成两个部分 \(x,y\),使得 \(x\lt v,y \ge v\)。然后合并回去。
代码:
void insert(int v){int left=0,right=0;split(root,v-1,left,right);root=merge(merge(left,newnode(v)),right);}
FHQ-Treap 其他操作——remove
\(\operatorname{remove}(v)\),在平衡树中删除元素 \(v\),如果有多个,删除其中的任意一个。
将平衡树分裂成 \(x,y,z\),使得 \(x\lt v,y=v,z\gt v\),将 \(y\) 的根删掉,然后合并 \(x,y,z\)。
代码:
void remove(int v){int left=0,mid=0,right=0;split(root,v,left,right);split(left,v-1,left,mid);mid=merge(ls(mid),rs(mid));root=merge(merge(left,mid),right);}
FHQ-Treap 的其他操作——rank
\(\operatorname{rank}(v)\),查询 \(v\) 在平衡树中的排名。
先将平衡树分裂成 \(x,y\),使得 \(x\lt v,y \ge v\),然后查询 \(x\) 的子树大小,加 \(1\) 就是答案,最后记得合并回去。
代码:
int rnk(int v){int left=0,right=0,ret;split(root,v-1,left,right);ret=siz[left]+1;root=merge(left,right);return ret;}
FHQ-Treap 的其他操作——kth,pre,nxt
由于本人弱,不想讲解。
代码:
int kth(int k){int now=root;while(1){if(k<=siz[ls(now)]){now=ls(now);}else if(k==siz[ls(now)]+1){return val[now];}else{k-=siz[ls(now)]+1;now=rs(now);}}}int pre(int v){int now=root,ret=0;while(1){if(!now){return ret;}else if(v=val[now]){now=rs(now);}else{ret=val[now];now=ls(now);}}}
例题代码
#include #define int long longusing namespace std;namespace FHQTreap{const int SIZE = 2e6+5;int son[SIZE][2];int val[SIZE],rk[SIZE],siz[SIZE],root,points;mt19937 randomer(time(0));#define ls(i) (son[(i)][0])#define rs(i) (son[(i)][1])void pushup(int i){siz[i]=siz[ls(i)]+siz[rs(i)]+1;}int newnode(int v){val[++points]=v;rk[points]=randomer();siz[points]=1;return points;}void split(int p,int v,int &left,int &right){if(!p){left=right=0;return;}if(val[p]<=v){left=p;split(rs(p),v,rs(left),right);}else{right=p;split(ls(p),v,left,ls(right));}pushup(p);}int merge(int left,int right){if(!left||!right){if(left)return left;else if(right)return right;else return 0;}if(rk[left]<rk[right]){ls(right)=merge(left,ls(right));pushup(right);return right;}else{rs(left)=merge(rs(left),right);pushup(left);return left;}}void insert(int v){int left=0,right=0;split(root,v-1,left,right);root=merge(merge(left,newnode(v)),right);}void remove(int v){int left=0,mid=0,right=0;split(root,v,left,right);split(left,v-1,left,mid);mid=merge(ls(mid),rs(mid));root=merge(merge(left,mid),right);}int kth(int k){int now=root;while(1){if(k<=siz[ls(now)]){now=ls(now);}else if(k==siz[ls(now)]+1){return val[now];}else{k-=siz[ls(now)]+1;now=rs(now);}}}int pre(int v){int now=root,ret=0;while(1){if(!now){return ret;}else if(v=val[now]){now=rs(now);}else{ret=val[now];now=ls(now);}}}int rnk(int v){int left=0,right=0,ret;split(root,v-1,left,right);ret=siz[left]+1;root=merge(left,right);return ret;}}namespace solution{int n,m,lastans,ret;int solution(){lastans=0;ret=0;cin>>n>>m;for(int i=1,v;i>v;FHQTreap::insert(v);}while(m--){int op,v;cin>>op>>v;if(op==1){v^=lastans;FHQTreap::insert(v);}else if(op==2){v^=lastans;FHQTreap::remove(v);}else if(op==3){v^=lastans;lastans=FHQTreap::rnk(v);ret^=lastans;}else if(op==4){v^=lastans;lastans=FHQTreap::kth(v);ret^=lastans;}else if(op==5){v^=lastans;lastans=FHQTreap::pre(v);ret^=lastans;}else{v^=lastans;lastans=FHQTreap::next(v);ret^=lastans;}}cout<<ret;return 0;}}signed main(){ ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);return solution::solution();}
AC Record
普通平衡树例题P2343 宝石管理系统
P2343 宝石管理系统
GY 君购买了一批宝石放进了仓库。有一天 GY 君心血来潮,想要清点他的宝石,于是把 \(m\) 个宝石都取出来放进了宝石管理系统。每个宝石 \(i\) 都有一个珍贵值 \(v_i\),他希望你能编写程序查找到从大到小第 \(n\) 珍贵的宝石。但是现在问题来了,他非常不小心的留了一些宝石在仓库里面,有可能要往现有的系统中添加宝石。这些宝石的个数比较少。他表示非常抱歉,但是还是希望你的系统能起作用。\(m\leq 100000\),\(q\leq 30000\)
这道题比较水,直接 \(\text{FHQ-Treap}\) 模拟。
时间复杂度 \(O(q\log m)\)
P2073 送花
见这里
P1503 鬼子进村
见这里
P3871 [TJOI2010]中位数
见这里
如果文章有问题,静待斧正,建议向我发送洛谷私信并指出博文地址 https://www.cnblogs.com/zheyuanxie/p/fhq-treap.html!