[模板] 平衡树: Splay, 非旋Treap, 替罪羊树
简介
二叉搜索树, 可以维护一个集合/序列, 同时维护节点的 \(size\), 因此可以支持 insert(v), delete(v), kth(p,k), rank(v)等操作.
另外, prev(v) == kth(rt,rank(v)-1);
next(v) == kth(rt,rank(v)+1).
平衡树通过各种方法保证二叉搜索树的平衡, 从而达到 \(O(\log n)\) 的均摊复杂度.
Splay
Splay 不仅可以实现一般平衡树的操作, 还可以实现序列的翻转/旋转等操作.
Splay 被用于LCT的操作, 保证了LCT的各种操作的复杂度也为 \(O(\log n)\).
有关 rotate


代码
以前的代码, 似乎不少地方都写麻烦了...
struct tnode{
int val,cnt,sz,fa,son[2];
tnode():val(0),cnt(0),sz(0),fa(0){son[0]=son[1]=0;}
tnode(int f,int v):val(v),cnt(1),sz(1),fa(f){son[0]=son[1]=0;}
};
const int sz=100010;
struct splay{
int rt,end;
tnode n[sz];
splay():rt(0),end(0) {}
int addnode(int f,int v){
n[++end]=tnode(f,v);
return end;
}
void update(int p){
n[p].sz=n[n[p].son[0]].sz+n[n[p].son[1]].sz+n[p].cnt;
}
void sf(int f,int s,int dir){
n[f].son[dir]=s;
n[s].fa=f;
}
void rotate(int p){
int x=n[p].fa,y=n[x].fa;
int dir=n[x].son[1]==p,dir2=n[y].son[1]==x;
int z=n[p].son[!dir];
sf(y,p,dir2);
sf(x,z,dir);
sf(p,x,!dir);
update(x);
update(p);
}
void sp(int p,int dist=0){
if(dist==0)rt=p;
for(int x=n[p].fa;x!=dist;x=n[p].fa){
if(n[x].fa!=dist)
rotate(((n[x].son[1]==p)^(n[n[x].fa].son[1]==x))?p:x);
rotate(p);
}
}
void insert(int val){
if(rt==0){
rt=addnode(0,val);
return;
}
int now=rt;
while(now){
if(n[now].val==val){
++n[now].cnt;
update(now);
break;
}
int dir=n[now].val<val;
if(n[now].son[dir])
now=n[now].son[dir];
else{
n[now].son[dir]=addnode(now,val);
now=n[now].son[dir];
break;
}
}
sp(now);
}
int min(int p){
int fa=n[p].fa;
while(n[p].son[0])
p=n[p].son[0];
sp(p,fa);
return p;
}
int max(int p){
int fa=n[p].fa;
while(n[p].son[1])
p=n[p].son[1];
sp(p,fa);
return p;
}
int find(int val){
int now=rt,pr=0;
while(now){
pr=now;
if(n[now].val==val)
break;
now=n[now].son[n[now].val<val];
}
sp(pr);
return now;
}
int findkth(int k){
int now=rt,pr=0;
while(now){
pr=now;
int lsz=n[n[now].son[0]].sz;
if(k>lsz&&k<=lsz+n[now].cnt)
break;
if(k<=lsz)
now=n[now].son[0];
else{
k-=lsz+n[now].cnt;
now=n[now].son[1];
}
}
sp(pr);
return now;
}
int rank(int val){
int k=find(val);
return k?n[n[k].son[0]].sz+1:-1;
}
void remove(int val){
if(find(val)==0)return;
if(n[rt].cnt>1){
--n[rt].cnt;
update(rt);
return;
}
int ls=n[rt].son[0],rs=n[rt].son[1];
if(ls==0&&rs==0){
rt=0;
return;
}
if((ls==0)^(rs==0)){
rt=(ls!=0?ls:rs);
n[rt].fa=0;
return;
}
int newrt=min(rs);
n[newrt].fa=0;
n[newrt].son[0]=n[rt].son[0];
n[n[rt].son[0]].fa=newrt;
rt=newrt;
update(rt);
}
int prev(int val){
find(val);
if(n[rt].val>=val)
return max(n[rt].son[0]);
return rt;
}
int next(int val){
find(val);
if(n[rt].val<=val)
return min(n[rt].son[1]);
return rt;
}
}s;
非旋Treap
Treap 通过随机权值的堆保证树高度为 \(O(\log n)\).
Treap 可以持久化. (并不会写)
代码
struct tn{int v,p,sz,ch[2];}fhq[200060];
int rt=0,pf=0;
int newnd(int v){fhq[++pf]=(tn){v,rand(),1,{0,0}};return pf;}
void update(int p){fhq[p].sz=fhq[fhq[p].ch[0]].sz+fhq[fhq[p].ch[1]].sz+1;}
void split(int rt,int v,int &tl,int &tr){
if(rt==0){tl=tr=0;return;}
if(fhq[rt].v<=v)
tl=rt,split(fhq[rt].ch[1],v,fhq[rt].ch[1],tr);
else
tr=rt,split(fhq[rt].ch[0],v,tl,fhq[rt].ch[0]);
update(rt);
}
int merge(int tl,int tr){
if(tl==0||tr==0)return tl+tr;
if(fhq[tl].p<=fhq[tr].p){
fhq[tl].ch[1]=merge(fhq[tl].ch[1],tr);
update(tl);
return tl;
}
else{
fhq[tr].ch[0]=merge(tl,fhq[tr].ch[0]);
update(tr);
return tr;
}
}
int kth(int &rt,int k){
int now=rt;
while(1){
int tmp=fhq[fhq[now].ch[0]].sz;
if(k<=tmp)now=fhq[now].ch[0];
else if(k==tmp+1)return now;
else now=fhq[now].ch[1],k-=tmp+1;
}
}
void insert(int &rt,int v){
int x,y;
split(rt,v,x,y);
rt=merge(merge(x,newnd(v)),y);
}
void remove(int &rt,int v){
int x,y,z;
split(rt,v,x,z);
split(x,v-1,x,y);
y=merge(fhq[y].ch[0],fhq[y].ch[1]);
rt=merge(merge(x,y),z);
}
int rank(int &rt,int v){
int x,y;
split(rt,v-1,x,y);
int tmp=fhq[x].sz+1;
rt=merge(x,y);
return tmp;
}
int prev(int &rt,int v){
return kth(rt,rank(rt,v)-1);
}
int next(int &rt,int v){
return kth(rt,rank(rt,v+1));
}
void print(){
printf("dbg rt=%d\n",rt);
printf("i v p sz ch[0] ch[1]\n");
rep(i,0,pf)printf("%d %d %d %d %d %d\n",i,fhq[i].v,fhq[i].p,fhq[i].sz,fhq[i].ch[0],fhq[i].ch[1]);
printf("dbgend\n");
}
替罪羊树
替罪羊树定义一个值 \(\alpha\), 如果左子树点数/右子树点数 > 整个子树点数\(\cdot \alpha\), 将这个子树重构.
根据势能分析, 均摊复杂度为单次操作 \(O(\log n)\) . 不会证
显然\(0.5 < \alpha < 1\), 但 \(\alpha\) 取值过大或过小都会影响代码运行效率. \(\alpha\) 可以取 \(0.7\), \(0.75\), \(0.8\) 等值, 效率没有明显差距. 这里取 \(\alpha = 0.75\).
由于替罪羊树就是有重构的二叉搜索树, 它较为容易实现, 并且常数较小.
由于替罪羊树仅仅依赖重构操作, 它还可以实现一些奇怪的操作, 比如 K-D Tree, 动态区间第k大(替罪羊树套权值线段树)等.
代码
一遍过真开心
const db alp=0.75;
//szp: number of points(includes deleted ones);
//szr: number of values(not include deleted ones; point*cnt)
struct tnd{int v,szr,szp,cnt,ch[2];}tree[nsz];
#define ls(p) tree[p].ch[0]
#define rs(p) tree[p].ch[1]
il bool isbad(int p){return tree[ls(p)].szp>tree[p].szp*alp||tree[rs(p)].szp>tree[p].szp*alp;}
il void pu(int p){
tree[p].szp=tree[ls(p)].szp+tree[rs(p)].szp+1;
tree[p].szr=tree[ls(p)].szr+tree[rs(p)].szr+tree[p].cnt;
}
int rt=0,pt=0,deled[nsz],pd=0;
il void init(int p,int v){tree[p]=(tnd){v,1,1,1,{0,0}};}
il int newnd(int v){
int p=(pd?deled[pd--]:++pt);
init(p,v);
return p;
}
int li[nsz],pl=0;
void pia(int p){
if(p==0)return;
pia(ls(p));
if(tree[p].cnt)li[++pl]=p;
else deled[++pd]=p;
pia(rs(p));
}
void build(int &rt,int rl,int rr){
if(rl>rr){rt=0;return;}//important
int mid=(rl+rr)>>1;
rt=li[mid];
build(ls(rt),rl,mid-1);
build(rs(rt),mid+1,rr);
pu(rt);
}
void rebuild(int &rt){
// printf("RB %d\n",rt);
pl=0;
pia(rt);
build(rt,1,pl);
}
void insert(int v,int &rt){
if(rt==0){rt=newnd(v);return;}
if(v==tree[rt].v)++tree[rt].cnt;
else if(v<tree[rt].v)insert(v,ls(rt));
else insert(v,rs(rt));
pu(rt);
if(isbad(rt))rebuild(rt);
}
void remove(int v,int rt){
if(rt==0)return;
if(v==tree[rt].v){if(tree[rt].cnt)--tree[rt].cnt;}
else if(v<tree[rt].v)remove(v,ls(rt));
else remove(v,rs(rt));
pu(rt);
}
int rk(int v,int rt){
int ans=1;
while(rt){
if(v==tree[rt].v){ans+=tree[ls(rt)].szr;break;}
else if(v<tree[rt].v)rt=ls(rt);
else ans+=tree[ls(rt)].szr+tree[rt].cnt,rt=rs(rt);
}
return ans;
}
int kth(int k,int rt){
int fl=0;
while(rt){
if(k<=tree[ls(rt)].szr)rt=ls(rt),fl=0;
else{
k-=tree[ls(rt)].szr;
if(k<=tree[rt].cnt)return tree[rt].v;
else k-=tree[rt].cnt,rt=rs(rt),fl=1;
}
}
return fl?1e8:-1e8;
}
int prev(int v){return kth(rk(v,rt)-1,rt);}
int next(int v){return kth(rk(v+1,rt),rt);}
用于测试的输入/输出
由于某谷样例较弱, 窝就造了一个...
// it also tests rebuild() func in Scapegoat Tree
// and invalid input (output -inf/inf in my code)
// sample input
21
1 5
1 4
1 5
2 4
1 3
1 2
1 1
1 0
4 3
3 4
3 6
1 15
1 14
1 13
1 12
1 11
5 5
6 5
5 0
6 15
4 20
// sample output
2
5
7
3
11
-100000000
100000000
100000000
// output with "RB" (aka rebuild())
i=1
i=2
i=3
i=4
i=5
i=6
i=7
RB 1
i=8
i=9
2
i=10
5
i=11
7
i=12
i=13
i=14
RB 3
i=15
i=16
i=17
3
i=18
11
i=19
-100000000
i=20
100000000
i=21
100000000
[模板] 平衡树: Splay, 非旋Treap, 替罪羊树的更多相关文章
- 2018.08.05 bzoj3223: Tyvj 1729 文艺平衡树(非旋treap)
传送门 经典的平衡树问题,之前已经用splay写过一次了,今天我突发奇想,写了一发非旋treap的版本,发现挺好写的(虽然跑不过splay). 代码: #include<bits/stdc++. ...
- 平衡树之非旋Treap
平衡树(二叉树) 线段树不支持插入or删除一个数于是平衡树产生了 常见平衡树:treap(比sbt慢,好写吧),SBT(快,比较好写,有些功能不支持),splay(特别慢,复杂度当做根号n来用,功能强 ...
- [Bzoj3224][Tyvj1728] 普通平衡树(splay/无旋Treap)
题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3224 平衡树入门题,学习学习. splay(学习yyb巨佬) #include<b ...
- [Bzoj3223][Tyvj1729] 文艺平衡树(splay/无旋Treap)
题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3223 平衡树处理区间问题的入门题目,普通平衡树那道题在维护平衡树上是以每个数的值作为维护 ...
- BZOJ - 3223 Tyvj 1729 文艺平衡树 (splay/无旋treap)
题目链接 splay: #include<bits/stdc++.h> using namespace std; typedef long long ll; ,inf=0x3f3f3f3f ...
- bzoj 3224: Tyvj 1728 普通平衡树【非旋treap】
就是非旋treap的板子 #include<iostream> #include<cstdio> #include<cstdlib> using namespace ...
- 平衡树简单教程及模板(splay, 替罪羊树, 非旋treap)
原文链接https://www.cnblogs.com/zhouzhendong/p/Balanced-Binary-Tree.html 注意是简单教程,不是入门教程. splay 1. 旋转: 假设 ...
- 非旋Treap总结 : 快过Splay 好用过传统Treap
非旋$Treap$ 其高级名字叫$Fhq\ Treap$,既然叫$Treap$,它一定满足了$Treap$的性质(虽然可能来看这篇的人一定知道$Treap$,但我还是多说几句:$Fhp\ Treap$ ...
- 非旋treap套线段树
BZOJ3065. 去年用pascal 块链过了.. 今年来试了试非旋treap大法 注定被块链完爆 代码留这. 第一份 :辣鸡的 垃圾回收做法 跑得极慢 #include <bits/ ...
随机推荐
- Fundebug发布Vue插件,简化BUG监控接入代码
摘要: 代码越短越好! 我们发布了fundebug-vue插件,可以简化Vue框架接入Fundebug的代码. Vue如何接入Fundebug 1. 安装fundebug-javascript与fun ...
- Java实现登录验证码
登录验证码 Servlet /* 从请求中获取数据,获取验证码的session的值转为String类型, 销毁,防止返回后验证码不刷新,重新验证成功 判断验证码是否相同(忽略大 ...
- 自然语言处理(nlp)比计算机视觉(cv)发展缓慢,而且更难!
https://mp.weixin.qq.com/s/kWw0xce4kdCx62AflY6AzQ 1. 抢跑的nlp nlp发展的历史非常早,因为人从计算机发明开始,就有对语言处理的需求.各种字符 ...
- Salesforce 小知识:大量“子记录”的处理方法
大量"子记录"的存放 例子:系统中导入了很多"联系人"(Contact)记录,它们没有具体所属的"客户"(Account)记录.那么我们就要 ...
- 在android studio中配置运行时签名
做项目的时候,有时需要用到第三方接口,而基本第三方接口都是要求我们要先进行签名.结果每次调试都得手动进行签名一次,实在麻烦.所以android studio提供了一种在运行的时候自动进行签名的方法,在 ...
- centos服务器如何监控访问ip,并将非法ip通过防火墙禁用
centos服务器如何监控访问ip,并将非法ip通过防火墙禁用 上周给朋友帮忙,上架了一款小游戏(年年有鱼),项目刚一上线,就遇到了ddos攻击,阿里云连续给出了6次ddos预警提示,服务器一度处于黑 ...
- Linux 安装 powershell
linux 安装 powershell Intro powershell 已经推出了一个 Powershell Core, 版本号对应 Powershell 6.x,可以跨平台,支持 Linux 和 ...
- Vue2.5笔记:Vue的实例与生命周期
理解与认识 Vue 的实例是我们学习 Vue 非常重要的一步,也是非常必须的,因为实例是它的一个起点,也是它的一个入口,只有我们创建一个 Vue 实例之后,我们才行利用它进行一些列的操作. 首先 Vu ...
- mas录屏,带系统声音和麦克风声音
自带的QuickTime + Soundflower 可完美解决,同时录系统的声音和mic声音,也可以只录系统声音. 安装Soundflower 在应用程序 -> 实用工具,里面找到“音频 MI ...
- 【笔记】嵩天.Python语言程序设计.完成两个简单实例(温度转换和绘图)
[博客导航] [Python相关] 目标 使用PyCharm,完成两个小实例的编写和运行.一个是温度转换,一个是蟒蛇图形绘制. 过程 1.先设置project目录,虽然命名不是很正式,主要不太习惯软件 ...