fhq_treap 小结
简介
\(fhq\_treap\)是一种非旋平衡树。在学习这篇文章之前,还是先学习一下普通\(treap\)吧
优点
相比于普通的\(treap\),它可以处理区间操作。
相比于\(splay\),它简洁易懂,代码也较短。
缺点
要比\(splay\)和\(treap\)慢
基础操作
\(fhq\_treap\)最基本的两个操作就是分裂和合并。
分裂
即把一个\(treap\)分为两个。有按照权值分和按照大小分两种方式。
具体方法:
比着代码划拉划拉就知道了(懒)。
按权值分
void split(int rt,int val,int &x,int &y) {
if(!rt) {
x = y = 0;
return;
}
if(TR[rt].w <= val) {
x = rt;split(rs,val,rs,y);
}
else {
y = rt;split(ls,val,x,ls);
}
update(rt);
}
按大小分
void split(int rt,int K,int &x,int &y) {
if(!rt) {
x = y = 0;return;
}
down(rt);
if(K <= TR[ls].siz) {
y = rt;split(ls,K,x,ls);
}
else {
x = rt;split(rs,K - TR[ls].siz - 1,rs,y);
}
update(rt);
}
合并
即把两个\(treap\)合并为一个。
注意要让id形成一个堆,且合并的两个树满足其中一个中的权值全部小于另一个。
具体方法:
比较简单,参考代码吧。。
int merge(int x,int y) {
if(!x || !y) return x + y;
if(TR[x].id < TR[y].id) {
TR[x].son[1] = merge(TR[x].son[1],y);
update(x);
return x;
}
TR[y].son[0] = merge(x,TR[y].son[0]);
update(y);
return y;
}
其他操作
插入
插入一个权值为\(x\)的数。
只需要将原来的\(treap\)按权值\(x\)分为\(L,R\)两棵树。
然后把\(x\)节点当成一个\(treap\)与\(L\)合并起来。然后再整体和\(R\)合并起来。
void insert(int x) {
int L,R;
split(root,x,L,R);
root = merge(merge(L,new_node(x)),R);
}
删除
删除一个权值为\(x\)的数。
将原来的\(treap\)按权值\(x\)分为\(L,R\)两棵子树。再按权值\(x-1\)将\(L\)分为\(L,rt\)两棵子树。
这时\(rt\)中就全都是权值为x的点了。删除根节点(也就是将根的两个孩子合并起来)。
操作完成别忘了合并回去。
void del(int x) {
int L,R,rt;
split(root,x,L,R);
split(L,x - 1,L,rt);
rt = merge(ls,rs);
root = merge(merge(L,rt),R);
}
查询排名
查询权值\(x\)的排名(定义为比\(x\)小的数的数量+1)。
将原\(treap\)按权值\(x-1\)分为\(L,R\)两棵子树。\(L\)的大小+1就是答案了。
操作完成别忘了合并回去。
int Rank(int x) {
int L,R;
split(root,x - 1,L,R);
int ret = TR[L].siz + 1;
root = merge(L,R);
return ret;
}
查询第k大
查询排名为\(k\)的数字。
如果排名小于等于左子树大小就查询左子树。
如果排名大于左子树大小+1就查询右子树,并且\(k-=\)左子树大小
否则返回当前节点。
int kth(int rt,int x) {
while(1) {
if(x == TR[ls].siz + 1) return rt;
if(x <= TR[ls].siz) rt = ls;
else {
x -= TR[ls].siz + 1;
rt = rs;
}
}
}
前驱
查询比\(x\)小的数中最大的数。
按权值\(x-1\)将原\(treap\)分为\(L,R\)两棵子树。
\(L\)子树中最大的那个就是答案。
操作完成别忘了合并回去。
int pre(int x) {
int L,R;
split(root,x - 1,L,R);
int ret = kth(L,TR[L].siz);
root = merge(L,R);return ret;
}
后继
查询比\(x\)大的数中最小的数。
按权值\(x\)将原\(treap\)分为\(L,R\)两棵子树。
\(R\)子树中最小的就是答案。
操作完成别忘了合并回去。
int nxt(int x) {
int L,R;
split(root,x,L,R);
int ret = kth(R,1);
root = merge(L,R);return ret;
}
处理区间
对区间\(l,r\)进行处理。
先按大小\(r\)将原\(treap\)分为\(L,R\)两棵子树。
再按大小\(l-1\)将\(L\)分为\(L1,L2\)两棵子树。
\(L2\)子树就是要处理的区间了。
操作完成别忘了合并回去。
void reverse(int l,int r) {
int L,R,rt,tmp;
split(root,r + 1,L,R);
split(L,l,tmp,rt);
TR[rt].rev ^= 1;
root = marge(marge(tmp,rt),R);
}
例题
/*
* @Author: wxyww
* @Date: 2019-04-13 08:47:22
* @Last Modified time: 2019-04-13 11:24:25
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
using namespace std;
typedef long long ll;
#define ls TR[rt].son[0]
#define rs TR[rt].son[1]
const int N = 100000 + 100;
ll read() {
ll x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
return x*f;
}
struct node {
int w,id,siz,son[2];
}TR[N];
int root,tot;
int new_node(int val) {
++tot;
TR[tot].w = val,TR[tot].id = rand(),TR[tot].siz = 1;
return tot;
}
void update(int rt) {
TR[rt].siz = TR[ls].siz + TR[rs].siz + 1;
}
int merge(int x,int y) {
if(!x || !y) return x + y;
if(TR[x].id < TR[y].id) {
TR[x].son[1] = merge(TR[x].son[1],y);
update(x);
return x;
}
TR[y].son[0] = merge(x,TR[y].son[0]);
update(y);
return y;
}
void split(int rt,int val,int &x,int &y) {
if(!rt) {
x = y = 0;
return;
}
if(TR[rt].w <= val) {
x = rt;split(rs,val,rs,y);
}
else {
y = rt;split(ls,val,x,ls);
}
update(rt);
}
void insert(int x) {
int L,R;
split(root,x,L,R);
root = merge(merge(L,new_node(x)),R);
}
void del(int x) {
int L,R,rt;
split(root,x,L,R);
split(L,x - 1,L,rt);
rt = merge(ls,rs);
root = merge(merge(L,rt),R);
}
int Rank(int x) {
int L,R;
split(root,x - 1,L,R);
int ret = TR[L].siz + 1;
root = merge(L,R);
return ret;
}
int kth(int rt,int x) {
while(1) {
if(x == TR[ls].siz + 1) return rt;
if(x <= TR[ls].siz) rt = ls;
else {
x -= TR[ls].siz + 1;
rt = rs;
}
}
}
int pre(int x) {
int L,R;
split(root,x - 1,L,R);
int ret = kth(L,TR[L].siz);
root = merge(L,R);return ret;
}
int nxt(int x) {
int L,R;
split(root,x,L,R);
int ret = kth(R,1);
root = merge(L,R);return ret;
}
int main() {
srand(time(0));
int n = read();
while(n--) {
int opt = read(),x = read();
if(opt == 1) insert(x);
else if(opt == 2) del(x);
else if(opt == 3) printf("%d\n",Rank(x));
else if(opt == 4) printf("%d\n",TR[kth(root,x)].w);
else if(opt == 5) printf("%d\n",TR[pre(x)].w);
else printf("%d\n",TR[nxt(x)].w);
}
return 0;
}
/*
* @Author: wxyww
* @Date: 2019-04-13 10:46:59
* @Last Modified time: 2019-04-13 11:24:56
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
using namespace std;
typedef long long ll;
#define ls TR[rt].son[0]
#define rs TR[rt].son[1]
const int N = 100000 + 100;
ll read() {
ll x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
return x*f;
}
struct node {
int w,id,siz,rev,son[2];
}TR[N];
int tot;
int root,n,m;
void update(int rt) {
TR[rt].siz = TR[ls].siz + TR[rs].siz + 1;
}
void down(int rt) {
if(TR[rt].rev) {
TR[ls].rev ^= 1;
TR[rs].rev ^= 1;
swap(ls,rs);
TR[rt].rev ^= 1;
}
}
int new_node(int x) {
++tot;
TR[tot].id = rand();TR[tot].siz = 1;TR[tot].w = x;TR[tot].rev = 0;
return tot;
}
int marge(int x,int y) {
if(!x || !y) return x + y;
down(x),down(y);
if(TR[x].id < TR[y].id) {
TR[x].son[1] = marge(TR[x].son[1],y);
update(x);
return x;
}
else {
TR[y].son[0] = marge(x,TR[y].son[0]);
update(y);
return y;
}
}
void split(int rt,int K,int &x,int &y) {
if(!rt) {
x = y = 0;return;
}
down(rt);
if(K <= TR[ls].siz) {
y = rt;split(ls,K,x,ls);
}
else {
x = rt;split(rs,K - TR[ls].siz - 1,rs,y);
}
update(rt);
}
void reverse(int l,int r) {
int L,R,rt,tmp;
split(root,r + 1,L,R);
split(L,l,tmp,rt);
TR[rt].rev ^= 1;
root = marge(marge(tmp,rt),R);
}
int build(int l,int r) {
if(l > r) return 0;
int mid = (l + r) >> 1;
int rt = new_node(mid - 1);
ls = build(l,mid - 1);
rs = build(mid + 1,r);
update(rt);
return rt;
}
void print(int rt) {
if(!rt) return;
down(rt);
print(ls);
if(TR[rt].w >= 1 && TR[rt].w <= n) printf("%d ",TR[rt].w);
print(rs);
}
int main() {
n = read(),m = read();
root = build(1,n + 2);
while(m--) {
int l = read(),r = read();
reverse(l,r);
}
print(root);
return 0;
}
fhq_treap 小结的更多相关文章
- 从零开始编写自己的C#框架(26)——小结
一直想写个总结,不过实在太忙了,所以一直拖啊拖啊,拖到现在,不过也好,有了这段时间的沉淀,发现自己又有了小小的进步.哈哈...... 原想框架开发的相关开发步骤.文档.代码.功能.部署等都简单的讲过了 ...
- Python自然语言处理工具小结
Python自然语言处理工具小结 作者:白宁超 2016年11月21日21:45:26 目录 [Python NLP]干货!详述Python NLTK下如何使用stanford NLP工具包(1) [ ...
- java单向加密算法小结(2)--MD5哈希算法
上一篇文章整理了Base64算法的相关知识,严格来说,Base64只能算是一种编码方式而非加密算法,这一篇要说的MD5,其实也不算是加密算法,而是一种哈希算法,即将目标文本转化为固定长度,不可逆的字符 ...
- iOS--->微信支付小结
iOS--->微信支付小结 说起支付,除了支付宝支付之外,微信支付也是我们三方支付中最重要的方式之一,承接上面总结的支付宝,接下来把微信支付也总结了一下 ***那么首先还是由公司去创建并申请使用 ...
- iOS 之UITextFiled/UITextView小结
一:编辑被键盘遮挡的问题 参考自:http://blog.csdn.net/windkisshao/article/details/21398521 1.自定方法 ,用于移动视图 -(void)mov ...
- K近邻法(KNN)原理小结
K近邻法(k-nearst neighbors,KNN)是一种很基本的机器学习方法了,在我们平常的生活中也会不自主的应用.比如,我们判断一个人的人品,只需要观察他来往最密切的几个人的人品好坏就可以得出 ...
- scikit-learn随机森林调参小结
在Bagging与随机森林算法原理小结中,我们对随机森林(Random Forest, 以下简称RF)的原理做了总结.本文就从实践的角度对RF做一个总结.重点讲述scikit-learn中RF的调参注 ...
- Bagging与随机森林算法原理小结
在集成学习原理小结中,我们讲到了集成学习有两个流派,一个是boosting派系,它的特点是各个弱学习器之间有依赖关系.另一种是bagging流派,它的特点是各个弱学习器之间没有依赖关系,可以并行拟合. ...
- scikit-learn 梯度提升树(GBDT)调参小结
在梯度提升树(GBDT)原理小结中,我们对GBDT的原理做了总结,本文我们就从scikit-learn里GBDT的类库使用方法作一个总结,主要会关注调参中的一些要点. 1. scikit-learn ...
随机推荐
- Ubuntu16.04安装opencv-3.4.2
原文链接: https://m.oldpan.me/archives/ubuntu-install-opencv-from-source 第一步:更新我们的系统 sudo apt-get update ...
- defer 和 async 区别
defer saync 共同点: script 标签属性, 控制脚本加载时间,解决script下载阻塞的问题. 区别: defer:推推推荐! 异步加载,所有元素解析完执行. async: 异步加载, ...
- 浏览器的同源策略及CORS跨域解决方案 DRF
一个源的定义 如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源. 举个例子: 下表给出了相对http://a.xyz.com/dir/page.html同源检测的示例: UR ...
- 简单的shell命令
grep echo 重定向与管道 tr 特殊文件:/dev/null,/dev/tty 基本命令查找 访问shell脚本的参数 简单的执行跟踪: set -x set +x
- 47.Odoo产品分析 (五) – 定制板块(2) – 为业务自定义odoo(2)
查看Odoo产品分析系列--目录 Odoo产品分析 (五) – 定制板块(2) – 为业务自定义odoo(1) 4 添加自定义字段 定制odoo的最普通的原因就是指定到公司的附加信息.如果您正在运行一 ...
- Jmeter调用自定义jar包
一. 场景 在测试过程中, 可能需要调用第三方jar包来生成测试数据或者使用java工具类来实现业务场景, 普遍的做法是手动调用jar包, 再把这些值赋给jmeter中的某个参数, 以满足业务测试需求 ...
- RowKey设计之单调递增行键/时序数据
在一个集群中,一个导入数据的进程锁住不动,所有的client都在等待一个region (因而也就是一个单个节点),过了一会后,变成了下一个region…如果使用了单调递增 或者时序的key便会造成 ...
- selenium-日志文件的使用(十二)
概述 在自动化测试中,如果测试失败需要对错误的错误的代码或者测试case进行分析,进行分析时最好的方法是在项目中添加日志文件,通过日志文件的分析定位出现错误的原因. 这样可以保持自动化测试用例的健壮性 ...
- SQL Server(1)数据库基础
一.数据库能够做什么 1.存储大量的数据. 2.保持数据信息的一致.完整. 3.共享和安全. 4.通过组合分析,产生新的有用信息. 二.数据库的基本概念 1.数据库就是“数据”的“仓库”. 2.数据库 ...
- 网页验证码出不来,读取验证码时出错:javax.imageio.IIOException: Can't create cache file!
版权声明:本文为博主原创文章,仅作为学习交流使用 转载请注明出处 https://www.cnblogs.com/linck/p/10593053.html 今天打开项目时,发现登陆界面的验证码出不来 ...