简介

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

    <!DOCTYPE html> <html> <head> <meta name="viewport" content="wid ...

  2. Lyndon Word学习笔记

    Lyndon Word 定义:对于字符串\(s\),若\(s\)的最小后缀为其本身,那么称\(s\)为Lyndon串 等价性:\(s\)为Lyndon串等价于\(s\)本身是其循环移位中最小的一个 性 ...

  3. jQuery ajax-param()

    param() 方法创建数组或对象的序列化表示. 该序列化值可在进行 AJAX 请求时在 URL 查询字符串中使用. 序列化一个 key/value 对象: var params = { width: ...

  4. 上海启动5G试用!104页PPT,为你深度解析5G终端的创新和机遇

    文章发布于公号[数智物语] (ID:decision_engine),关注公号不错过每一篇干货. 来源:国泰君安证券 作者:分析师王聪.张阳.陈飞达 导读:2019年是5G元年,各大品牌将陆续推出5G ...

  5. 【阿里云】在 Windows Server 2016 下使用 FileZilla Server 安装搭建 FTP 服务

     Windows Server 2016 下使用 FileZilla Server 安装搭建 FTP 服务 一.安装 Filezilla Server 下载最新版本的 Filezilla Server ...

  6. Docker 镜像编排并部署SpringBoot应用

    Docker-compose是一个基于Docker的编排工具,所谓编排个人理解就是将不同的镜像通过配置,组成一个新的运行环境,官方定义是:Compose is a tool for defining ...

  7. Linux中ftp的常用命令

    转自:https://www.jb51.net/article/103904.htm FTP命令 ftp> ascii # 设定以ASCII方式传送文件(缺省值) ftp> bell # ...

  8. 比较器 comparable与comparator用法

    comparable 接口 Comparable<T> 类型参数:T - 可以与此对象进行比较的那些对象的类型 public interface Comparable<T> 此 ...

  9. 委托学习总结(一)浅谈对C#委托理解

    初入社会,对于我这个初级程序员来说要学的东西实在太多了,公司最近在做一个winform框架开发的桌面应用程序,众所周知,winform也好,webform也好,里面随处可见的事件驱动,有事件,当然也少 ...

  10. selenium Python 总结一些工作中可能会经常使用到的API。

    selenium Python 总结一些工作中可能会经常使用到的API. 1.获取当前页面的Url 方法:current_url 实例:driver.current_url 2.获取元素坐标 方法:l ...