[模板] 平衡树: 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/ ...
随机推荐
- Html5: Drawing with text
<!DOCTYPE html> <html> <head> <meta name="viewport" content="wid ...
- Lyndon Word学习笔记
Lyndon Word 定义:对于字符串\(s\),若\(s\)的最小后缀为其本身,那么称\(s\)为Lyndon串 等价性:\(s\)为Lyndon串等价于\(s\)本身是其循环移位中最小的一个 性 ...
- jQuery ajax-param()
param() 方法创建数组或对象的序列化表示. 该序列化值可在进行 AJAX 请求时在 URL 查询字符串中使用. 序列化一个 key/value 对象: var params = { width: ...
- 上海启动5G试用!104页PPT,为你深度解析5G终端的创新和机遇
文章发布于公号[数智物语] (ID:decision_engine),关注公号不错过每一篇干货. 来源:国泰君安证券 作者:分析师王聪.张阳.陈飞达 导读:2019年是5G元年,各大品牌将陆续推出5G ...
- 【阿里云】在 Windows Server 2016 下使用 FileZilla Server 安装搭建 FTP 服务
Windows Server 2016 下使用 FileZilla Server 安装搭建 FTP 服务 一.安装 Filezilla Server 下载最新版本的 Filezilla Server ...
- Docker 镜像编排并部署SpringBoot应用
Docker-compose是一个基于Docker的编排工具,所谓编排个人理解就是将不同的镜像通过配置,组成一个新的运行环境,官方定义是:Compose is a tool for defining ...
- Linux中ftp的常用命令
转自:https://www.jb51.net/article/103904.htm FTP命令 ftp> ascii # 设定以ASCII方式传送文件(缺省值) ftp> bell # ...
- 比较器 comparable与comparator用法
comparable 接口 Comparable<T> 类型参数:T - 可以与此对象进行比较的那些对象的类型 public interface Comparable<T> 此 ...
- 委托学习总结(一)浅谈对C#委托理解
初入社会,对于我这个初级程序员来说要学的东西实在太多了,公司最近在做一个winform框架开发的桌面应用程序,众所周知,winform也好,webform也好,里面随处可见的事件驱动,有事件,当然也少 ...
- selenium Python 总结一些工作中可能会经常使用到的API。
selenium Python 总结一些工作中可能会经常使用到的API. 1.获取当前页面的Url 方法:current_url 实例:driver.current_url 2.获取元素坐标 方法:l ...