title : 平衡树专题之替罪羊树
date : 2022-2-8
tags : ACM,数据结构,平衡树
author : Linno

替罪羊树

替罪羊树是一种非常优(bao)雅(li)的平衡树,与普通平衡树的区别是,它维护平衡的方式不是旋转,而是重构。尽管重构的过程是非常暴力的,复杂度依然是很好的O(logn)O(logn)O(logn)

准备工作

需要储存的信息:左右子树编号、当前结点的值、以当前结点为根的树的大小和实际大小、删除标记。

struct Nodeint l,r,val; //左右子树编号和结点的值int sz,fact; //树的大小和<实际大小>bool exist; //删除标记
}tzy[maxn]
新建节点

在所在结点now建立一个值为val的结点。

void newnode(int &now,int val){now=++cnt;tzy[now].val=val;tzy[now].sz=tzy[now].fact=1;tzy[now].ex=1;
}
插入结点

与平衡树基本操作一致。

void ins(int &now,int val){if(!now){newnode(now,val);//从now开始插入valcheck(root,now); //从root到now检查是否重构  return;}tzy[now].sz++;tzy[now].fact++;if(val<tzy[now].val) ins(tzy[now].l,val); //往左边插入else ins(tzy[now].r,val); //往右边插入
}
删除操作

在将要被删除的结点上打一个标记,称之为“惰性删除”。

void del(int &now,int val){if(tzy[now].exist&&tzy[now].val==val){tzy[now].exist=false;  //打删除标记tzy[now].fact--;check(root,now)//检查是否平衡return;}tzy[now].fact--; if(val<tzy[now].val) del(tzy[now].l,val);else del(tzy[now].r,val);
}
检查并判断是否重构

进行插入和删除之后需要检查树是否需要重构。

需要重构的条件:当前结点的左子树或右子树的大小大于当前结点的大小乘一个平衡因子alpha(一般在0.5~1之间)

平衡因子alpha必须取0.5~1之间的数比如0.75

这里解释为什么要区分size和fact:被删除结点的过多也会影响后续操作的效率。

bool imbalance(int now){ //判断当前结点是否平衡if(max(tzy[tzy[now].l].size,tzy[tzy[now].r].size)>tzy[now].size*alpha||tzy[now].size-tzy[now].fact>tzy[now].size*0.3) return true;return false;
}void update(int now,int end){if(!now) return;if(tzy[end].val<tzy[now].val) update(tzy[now].l,end);else update(tzy[now].r,end);tzy[now].size=tzy[tzy[now].l].size+tzy[tzy[now].r].size+1;
}void check(int &now,int end){ //检查当前结点到end的树是否平衡 if(now==end) return;if(imbalence(now)){ //如果不平衡rebuild(now); //重构now结点update(root,now); //更新return;}else{  //检查下一个结点if(tzy[end].val<tzy[now].val) check(tzy[now].l,end);else check(tzy[now].r,end);}
}
重构

这个过程非常暴力,同时是替罪羊树的核心。大致思路是:首先将当前子树进行中序遍历拉成直线,然后分治拎起来。

vector<int>v; //先把子树的所有结点放进vector中
void ldr(int now){ //中序遍历if(!now) reuturn;ldr(tyz[now].l);if(tzy[now].exist) v.push_back(now);ldr(tzy[now].r);
}void lift(int l,int r,int &now){if(l==r){now=v[l];tzy[now].l=tzy[now].r=0;tzy[now].sz=tzy[now].fact=1;return;}int m=l+r>>1;while(l<m&&tzy[v[m]].val==tzy[v[m-1]].val) m--;now=v[m];if(l<m) lift(l,m-1,tzy[now].l); //抬左区间else tzy[now].l=0;lift(m+1,r,tzy[now].r); //抬有区间tzy[now].sz=tzy[tzy[now].l].sz+tzy[tzy[now].r].sz+1;tzy[now].fact=tzy[tzy[now].l].fact+tzy[tzy[now].r].fact+1; //这个也要更新
}void rebuild(int &now){v.clear();ldr(now);  //求中序遍历if(v.empty()){now=0;return;}lift(0,v.size()-1,now);
}void update(int now,int end){ //有点线段树内味if(!now) return;if(tzy[end].val<tzy[now].val) update(tzy[now].l,end);else update(tzy[now].r,end);tzy[now].sz=tzy[tzy[now].l].sz+tzy[tzy[now].r].sz+1;
}
查询排名/数

与一般平衡树的操作基本一致,不过进行处理的是fact而不是size。

int getrank(int val){int now=root,rank=1;while(now){if(val<=tzy[now].val) now=tzy[now].l;else{rank+=tzy[now].exist+tzy[tzy[now].l].fact;now=tzy[now].r;}}return rank;
}int getnum(int rank){int now=root;while(now){if(tyz[now].exist&&tzy[tzy[now].l].fact+tzy[now].exist==rank) break;else if(tzy[tzy[now].l].fact>=rank) now=tzy[now].l;else{rank-=tzy[tzy[now].l].fact+tzy[now].exist;now=tzy[now].r;}}return tzy[now].val;
}
求前驱和后继
printf(getnum(getrank(x)-1));//直接求前驱
printf(getnum(getrank(x+1)));//直接求后继

模板

luoguP3369 【模板】普通平衡树
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+7;
const double alpha=0.75;int n,op,x,st,rt,cnt,tot,cur[N+5],Void[N+5];struct node{int ch[2],ex,v,sz,fc;
}tzy[N+5];inline void Init(){  //初始化 tot=0;for(register int i=N-1;i;--i) Void[++tot]=i; //用来回收垃圾的
}inline bool balance(int x){ //是否平衡 return (double) tzy[x].fc*alpha>(double)max(tzy[tzy[x].ch[0]].fc,tzy[tzy[x].ch[1]].fc);
}inline void Build(int x){ //新建节点 tzy[x].ch[0]=tzy[x].ch[1]=0,tzy[x].sz=tzy[x].fc=1;
}inline void Insert(int &x,int v){ //插入结点 if(!x){ x=Void[tot--],tzy[x].v=v,tzy[x].ex=1,Build(x);return;}++tzy[x].sz,++tzy[x].fc; if(v<=tzy[x].v) Insert(tzy[x].ch[0],v);else Insert(tzy[x].ch[1],v);
}
inline void PushUp(int x){ //更新操作 tzy[x].sz=tzy[tzy[x].ch[0]].sz+tzy[tzy[x].ch[1]].sz+1;tzy[x].fc=tzy[tzy[x].ch[0]].fc+tzy[tzy[x].ch[1]].fc+1;
}
inline void Traversal(int x){ //中序遍历 if(!x) return;Traversal(tzy[x].ch[0]);if(tzy[x].ex) cur[++cnt]=x;else Void[++tot]=x; //废品回收站 Traversal(tzy[x].ch[1]);
}
inline void SetUp(int l,int r,int &x){ //将链拎起来成树 int mid=l+r>>1;x=cur[mid];if(l==r){Build(x);return;}if(l<mid) SetUp(l,mid-1,tzy[x].ch[0]);else tzy[x].ch[0]=0;SetUp(mid+1,r,tzy[x].ch[1]),PushUp(x);
}
inline void ReBuild(int &x){ //暴力重构 cnt=0,Traversal(x);if(cnt) SetUp(1,cnt,x);else x=0;
}
inline void check(int x,int v){ //检查x到v的路径 int s=((v<=tzy[x].v)?0:1);while(tzy[x].ch[s]){if(!balance(tzy[x].ch[s])) {ReBuild(tzy[x].ch[s]);return;}x=tzy[x].ch[s],s=v<=tzy[x].v?0:1;}
}
inline int get_rank(int v){ //获取排名 int x=rt,rk=1;while(x){if(tzy[x].v>=v) x=tzy[x].ch[0];else rk+=tzy[tzy[x].ch[0]].fc+tzy[x].ex,x=tzy[x].ch[1];}return rk;
}
inline int get_val(int rk){ //获取第rk大的数 int x=rt;while(x){if(tzy[x].ex&&tzy[tzy[x].ch[0]].fc+1==rk) return tzy[x].v;else if(tzy[tzy[x].ch[0]].fc>=rk) x=tzy[x].ch[0];else rk-=tzy[x].ex+tzy[tzy[x].ch[0]].fc,x=tzy[x].ch[1];}
}
inline void Delete(int &x,int rk){ //下传删除 if(tzy[x].ex&&!((tzy[tzy[x].ch[0]].fc+1)^rk)){tzy[x].ex=0,--tzy[x].fc;return;}--tzy[x].fc;if(tzy[tzy[x].ch[0]].fc+tzy[x].ex>=rk) Delete(tzy[x].ch[0],rk);else Delete(tzy[x].ch[1],rk-tzy[x].ex-tzy[tzy[x].ch[0]].fc);
}inline void del(int v){ //删除操作 Delete(rt,get_rank(v));if(!balance(rt)) ReBuild(rt); //不平衡则需要重构
}signed main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n;Init();while(n--){cin>>op>>x;if(op==1){st=rt;Insert(rt,x);check(st,x);}else if(op==2){del(x);}else if(op==3){cout<<get_rank(x)<<"\n";}else if(op==4){cout<<get_val(x)<<"\n";}else if(op==5){cout<<get_val(get_rank(x)-1)<<"\n";}else if(op==6){cout<<get_val(get_rank(x+1))<<"\n";}}return 0;
}

参考材料

https://blog.csdn.net/a_forever_dream/article/details/81984236

https://www.bilibili.com/video/BV1Wt411L7te

https://oi-wiki.org/ds/sgt/

https://www.cnblogs.com/chenxiaoran666/p/ScapegoatTree.html

【算法竞赛学习笔记】平衡树专题——替罪羊树相关推荐

  1. 【算法竞赛学习笔记】pb_ds-超好懂的数据结构

    title : pb_ds date : 2021-8-21 tags : ACM,数据结构 author : Linno 简介 pb_ds库全称Policy-Based Data Structure ...

  2. 【算法竞赛学习笔记】快速傅里叶变换FFT-数学提高计划

    tilte : 快速傅里叶变换FFT学习笔记 tags : ACM,数论 date : 2021-7-18 简介 FFT(Fast Fourier Transformation),中文名快速傅里叶变换 ...

  3. 【算法竞赛学习笔记】超好懂的斯坦纳树详解!!!

    title : 斯坦纳树 tags : ACM 图论 date : 2021-6-26 author : Linno 什么是斯坦纳树 给定 n 个点 A1,A2,⋯,An试求连接此n个点,总长最短的直 ...

  4. 【算法竞赛学习笔记】Link-Cut-Tree基础-超好懂的数据结构

    titile : Link-Cut-Tree time : 2021-7-21 tags : ACM,数据结构 author : Linno LCT Link-Cut-Tree,中文名为动态树,是一种 ...

  5. 【算法竞赛学习笔记】KD-Tree

    title : KD-Tree date : 2022-4-7 tags : ACM,数据结构 author : Linno K-D tree K-D树是在k维欧几里得空间中组织点的数据结构.在算法竞 ...

  6. 【算法竞赛学习笔记】状压DP

    title : 状压DP date : 2022-3-5 tags : ACM,图论,动态规划 author : Linno 状压DP 状态压缩,是利用二进制数的性质对问题进行优化的一种算法,经常与搜 ...

  7. 【算法竞赛学习笔记】莫队算法-超优雅的暴力算法

    title : 莫队算法 tags : ACM,暴力 date : 2021-10-30 author : Linno 普通莫队 常用操作:分块/排序/卡常/离散化等,直接上板子. luoguP270 ...

  8. 【算法竞赛学习笔记】离散对数与BSGS-数学提升计划

    title : 离散对数与BSGS date : 2021-8-12 tags : ACM,数论 author Linno 阶 对与m互质的整数a,我们记满足an≡1modma^n\equiv 1\m ...

  9. 【算法竞赛学习笔记】后缀自动机SAM-超经典的字符串问题详解

    title : 后缀自动机 date : 2021-11-11 tags : ACM,字符串 author : Linno 前置知识 KMP,Trie,AC自动机等字符串基础 DFA(有限状态自动机) ...

  10. 【算法竞赛学习笔记】佩尔方程-数学提升计划

    title : 佩尔方程 date : 2021-10-31 tags : ACM,数学 author : Linno 佩尔方程 形如x2−dy2=1(d>1且d不为完全平方数)x^2-dy^2 ...

最新文章

  1. 【原创视频教程】学生信息管理系统5--学员成绩管理
  2. python 爬预警没解析前的
  3. hdu 4302 STL multiset
  4. mysql 中报1062_mysql error 1062 问题解决
  5. USING HAVING
  6. 用JavaScript实现一个Timeline
  7. [转载] python 中numpy快速去除nan, inf的方法
  8. /sbin/mount.vboxsf: mounting failed with the error: Protocol error
  9. Qt Embedded性能优化详解
  10. AutoCAD快速入门(二十九):视口
  11. HDU 5294 Tricks Device(多校2015 最大流+最短路)
  12. 嵌入式系统 ---> 程序存储器和数据存储器
  13. 架构问题--削峰填谷
  14. C++中的CopyElision
  15. 骨传导加动圈,这款Dacom耳机有何出众之处?开箱验证
  16. html5视频标签video画中画几个API
  17. 广东计算机二级考试大纲,全国计算机等级考试大纲(二级)
  18. 中国科学技术大学计算机研究生在哪个校区,中国科学技术大学有几个校区及校区地址 哪个校区最好...
  19. App高危漏洞 Android用户记得更新百度全家桶
  20. 搭建mongodb分布式集群(分片集群+keyfile安全认证以及用户权限)

热门文章

  1. 2019西安交大计算机专业研究生分数线,西安交通大学2019年考研分数线公布
  2. DOA算法3:Matrix Pencil
  3. python如何检验显著性差异_Python中如何计算Pearson相关性和显著性?
  4. RS485以及MODBUS知识积累
  5. FPGA实现全流水arccos,arcsin,任意次开放操作
  6. 软件测试中的版本控制
  7. oracle expdp作业外表报错ORA-20011KUP-11024ORA-29913
  8. 多线程读文本写入OracleNoSQL数据库
  9. linux 笔记本摄像头,Ubuntu 7.10 下屏蔽笔记本摄像头的方法
  10. 数据分析师真实的工作是怎样的,这篇文章带你看他们的职责