简介

二叉搜索树, 可以维护一个集合/序列, 同时维护节点的 \(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

Before

After

代码

以前的代码, 似乎不少地方都写麻烦了...

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, 替罪羊树的更多相关文章

  1. 2018.08.05 bzoj3223: Tyvj 1729 文艺平衡树(非旋treap)

    传送门 经典的平衡树问题,之前已经用splay写过一次了,今天我突发奇想,写了一发非旋treap的版本,发现挺好写的(虽然跑不过splay). 代码: #include<bits/stdc++. ...

  2. 平衡树之非旋Treap

    平衡树(二叉树) 线段树不支持插入or删除一个数于是平衡树产生了 常见平衡树:treap(比sbt慢,好写吧),SBT(快,比较好写,有些功能不支持),splay(特别慢,复杂度当做根号n来用,功能强 ...

  3. [Bzoj3224][Tyvj1728] 普通平衡树(splay/无旋Treap)

    题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3224 平衡树入门题,学习学习. splay(学习yyb巨佬) #include<b ...

  4. [Bzoj3223][Tyvj1729] 文艺平衡树(splay/无旋Treap)

    题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3223 平衡树处理区间问题的入门题目,普通平衡树那道题在维护平衡树上是以每个数的值作为维护 ...

  5. BZOJ - 3223 Tyvj 1729 文艺平衡树 (splay/无旋treap)

    题目链接 splay: #include<bits/stdc++.h> using namespace std; typedef long long ll; ,inf=0x3f3f3f3f ...

  6. bzoj 3224: Tyvj 1728 普通平衡树【非旋treap】

    就是非旋treap的板子 #include<iostream> #include<cstdio> #include<cstdlib> using namespace ...

  7. 平衡树简单教程及模板(splay, 替罪羊树, 非旋treap)

    原文链接https://www.cnblogs.com/zhouzhendong/p/Balanced-Binary-Tree.html 注意是简单教程,不是入门教程. splay 1. 旋转: 假设 ...

  8. 非旋Treap总结 : 快过Splay 好用过传统Treap

    非旋$Treap$ 其高级名字叫$Fhq\ Treap$,既然叫$Treap$,它一定满足了$Treap$的性质(虽然可能来看这篇的人一定知道$Treap$,但我还是多说几句:$Fhp\ Treap$ ...

  9. 非旋treap套线段树

    BZOJ3065. 去年用pascal 块链过了.. 今年来试了试非旋treap大法   注定被块链完爆 代码留这. 第一份 :辣鸡的  垃圾回收做法  跑得极慢 #include <bits/ ...

随机推荐

  1. 前端面试:谈谈 JS 垃圾回收机制

    摘要: 不是每个人都回答的出来... 最近看到一些面试的回顾,不少有被面试官问到谈谈JS 垃圾回收机制,说实话,面试官会问这个问题,说明他最近看到一些关于 JS 垃圾回收机制的相关的文章,为了 B 格 ...

  2. 单元测试_JUnit常用单元测试注解介绍及代码演示

    JUnit常用单元测试注解介绍及代码演示   by:授客 QQ:1033553122 1. 测试环境 1 2. 基础概念 1 3. 常用Annotation 1 4. 运行环境配置 3 maven配置 ...

  3. HTTP与HTTPS介绍

    文章大纲 一.HTTP和HTTPS的基本概念二.HTTP缺点三.HTTPS介绍四.免费HTTPS证书推荐   一.HTTP和HTTPS的基本概念 HTTP:是互联网上应用最为广泛的一种网络协议,是一个 ...

  4. MAC MAMP 中安装配置使用 ThinkPHP

    MAMP PRO 是Mac OS X 平台上经典的本地环境应用 MAMP 的专业版.专门为专业的Web开发人员和程序员轻松地安装和管理自己的开发环境. MAMP这几个首字母代表Mac OS X系统上的 ...

  5. electron入坑指南

    electron入坑指南 简介 electron 实际集成chrome浏览器和node环境, 运行你写的网页 app 基本目录结构 index.html 名称可以不是index, 这个文件与普通网页的 ...

  6. Truffle 4.0、Geth 1.7.2、TestRPC在私有链上搭建智能合约

    目录 目录 1.什么是 Truffle? 2.适合 Truffle 开发的客户端 3.Truffle的源代码地址 4.如何安装? 4.1.安装 Go-Ethereum 1.7.2 4.2.安装 Tru ...

  7. nginx配置静态项目

    当nignx可以加载下面的这个service时 server { listen 8085;  server_name 1.192.60.82; location / { root /etc/nginx ...

  8. SQL 使用临时表和临时变量完成update表字段---实际案例

    -- 使用临时表 -- 创建临时表 --ALTER TABLE TS_ExpenseApplication_Reim_Detail ADD BgCode NVARCHAR() NULL, BgItem ...

  9. BCP SQL导出EXCEL常见问题及解决方法;数据导出存储过程

    一.‘xp_cmdshell’的启用 SQL Server阻止了对组件‘xp_cmdshell’的过程‘sys.xp_cmdshell’的访问.因为此组件已作为此服务嚣安全配置的一部分而被关 闭.系统 ...

  10. windows搭建golang环境

    由于墙的存在,很多网址无法下载,推荐https://studygolang.com/dl去下载. windows需要配置几个环境变量,我是下载的压缩文件,所以需要自己配置,通过安装程序安装的应该不需要 ...