爆炸的平衡树, 替罪羊树

news/2024/12/27 2:57:35/文章来源:https://www.cnblogs.com/defad-ak-ioi/p/18634444

爆炸的平衡树, 替罪羊树

由于Defad不太喜欢旋转, 所以一般用替罪羊树. 这里写个博客介绍一下.

什么是二叉搜索树

可以维护一个集合, 相比于权值线段树 (动态开点) 的时间复杂度 \(\log{N}\) 空间复杂度 \(N \log{N}\), 二叉搜索树理论上来说只需要 \(\log{N}\) 的时间复杂度 (最坏是 \(N\)), 但是空间复杂度可以达到 \(N\).

这里不过多胡扯, 只能说普通二叉搜索树时间复杂度期望 \(\log{N}\) 最坏是 \(N\).

权值数据结构水各种题

什么是替罪羊树

考虑优化刚才的二叉搜索树, 可以带着标题里的 "爆炸" 进行考虑.

替罪羊树的想法是, 当有一个结点左子树和右子树差距过大, 可以炸掉这个结点的子树, 然后重新构造.

记录子树大小

我们每个结点用 \(3\) 个变量记录子树大小, 分别维护元素数, 未删除结点数, 总结点数.

这么说似乎有点抽象, 就是说,

  • \(sz0_{p}\) 维护这个子树里面有多少元素 (当然, 如果不是可重集就不用这个, 因为重复就是删除).
  • \(sz1_{p}\) 维护这个子树里有多少结点还没有被删除, 被删除的不计.
  • \(sz2_{p}\) 维护这个子树里面实际有多少结点, 被删除的也要记录.

结点 \(p\) 的值被删除时我们仅给了 \(cnt_{p} := \max(cnt_{p} - 1, 0)\), 炸子树时就无需打印了.

void push_up(int p){tr[p].sz0=tr[tr[p].ls].sz0+tr[tr[p].rs].sz0+tr[p].cnt;tr[p].sz1=tr[tr[p].ls].sz1+tr[tr[p].rs].sz1+(tr[p].cnt?1:0);tr[p].sz2=tr[tr[p].ls].sz2+tr[tr[p].rs].sz2+1;
}

爆炸

我们先不考虑怎么判断平衡, 考虑如何炸掉一个结点及其子树.

二叉搜索树的中序遍历是单调的, 那么我们可以打印中序遍历到一个数组里, 这里我们选择记录下标, 就不用记录值和次数, 然后申请很多结点去构造子树了, 只需要更改左右儿子指针和子树大小即可.

void squib(int p){if(p==0){return;}squib(tr[p].ls);if(tr[p].cnt){g[++cntg]=p;}squib(tr[p].rs);
}

这个函数在 debug 的时候可以直接炸掉根 \(rt\) 然后不重构, 然后挨个输出 \(i \in [1, cntg]\)\(val_{g_{i}}\).

void print(){squib(rt);f1(i,1,cntg,1){cout<<tr[g[i]].val<<" \n"[i==cntg];}
}

愣着干什么, 重构啊

毕竟都炸完了, 重构吧.

重构基本和线段树建树一样, 区别仅仅是当前结点是 \(mid\), 然后左子树只是 \([1, mid - 1]\) 了.

虽然我讲线段树也没说过建树, 当时说调用 \(N\) 次修改即可.

线段树, 算法竞赛掌管区间的神

我再说一遍, 如果我在参数里写了指针, 那么我的建议还是传引用, int &p 后面就不需要解引用了.

void build(int *p,int l,int r){if(l>r){*p=0;return;}if(l==r){tr[g[l]].ls=tr[g[l]].rs=0;push_up(g[l]);*p=g[l];return;}int m=l+r>>1;build(&tr[g[m]].ls,l,m-1);build(&tr[g[m]].rs,m+1,r);push_up(g[m]);*p=g[m];
}
void rebuild(int *p){cntg=0;squib(*p);build(p,1,cntg);
}

什么情况下就不够平衡, 需要重构呢?

首先, 每次插入元素和删除元素就有可能不平衡, 而查询 \(k\) th 和查询排名并不对树产生修改 (前驱后继都是用这个做的), 所以不可能需要重构.

考虑完什么时候有可能重构, 那么考虑在什么情况下重构.

替罪羊树考虑的是, 引入一个平衡因子 \(\alpha\), 在不满足 \(\alpha\) 的条件时重构子树.

又臭又长, 但是比旋转好记多了, 也不害怕写挂, 反正写挂最多写成普通二叉搜索树.

int check(int p){return tr[p].sz0&&(tr[p].sz2*alpha<=max(tr[tr[p].ls].sz2,tr[tr[p].rs].sz2)||alpha*tr[p].sz2>=tr[p].sz1);
}

然后在我们的插入元素和删除元素的递归的最后加上这个.

if(check(*p)){rebuild(p);
}

插入元素和删除元素

普通的二叉搜索树插入, 删除的时候给结点的 \(cnt_{p} := \max(cnt_{p} - 1, 0)\), 爆炸时如果 \(cnt_{p} = 0\) 则不打印.

ifelse ifelse 千万不能乱.

void push(int *p,int k){if(*p==0){*p=++cntt;tr[*p].val=k;tr[*p].cnt=1;push_up(*p);return;}else if(k==tr[*p].val){tr[*p].cnt++;}else if(k<tr[*p].val){push(&tr[*p].ls,k);}else{push(&tr[*p].rs,k);}push_up(*p);if(check(*p)){rebuild(p);}
}
void pop(int *p,int k){if(*p==0){return;}if(k<tr[*p].val){pop(&tr[*p].ls,k);}else if(k==tr[*p].val){tr[*p].cnt=max(tr[*p].cnt-1,0);}else{pop(&tr[*p].rs,k);}push_up(*p);if(check(*p)){rebuild(p);}
}

\(k\) th 和排名

类似线段树上二分, 这里用二叉搜索树上二分, 并不难.

需要注意的是 \(x\) 的排名是 rk(rt,x-1)+1 也就是最后一个比 \(x\) 小的元素的排名 \(+ 1\) 就是 \(x\) 的排名.

这么写表面是因为致敬权值线段树, 实际还是不习惯平衡树.

int kth(int p,int k){if(p==0){return -1;}else if(k<=tr[tr[p].ls].sz0){return kth(tr[p].ls,k);}else if(k<=tr[tr[p].ls].sz0+tr[p].cnt){return tr[p].val;}else{return kth(tr[p].rs,k-(tr[tr[p].ls].sz0+tr[p].cnt));}
}
int rk(int p,int k){if(p==0){return 0;}else if(k<tr[p].val){return rk(tr[p].ls,k);}else if(k==tr[p].val){return tr[tr[p].ls].sz0+tr[p].cnt;}else{return tr[tr[p].ls].sz0+tr[p].cnt+rk(tr[p].rs,k);}
}

前驱后继

不多说, 直接放代码, 理解起来很容易, 如果你用权值线段树水过平衡树板子.

kth(rt,rk(rt,x-1)) // 前驱
kth(rt,rk(rt,x)+1) // 后继

例题

平衡树

VJugde-LuoGu LuoGu

VJudge-DarkBZOJ DarkBZOJ

Tyvj 为什么找不到了 555

这里还是只给 main 函数.

read(&Q);
while(Q--){read(&op);if(op==1){read(&x);push(&rt,x);}else if(op==2){read(&x);pop(&rt,x);}else if(op==3){read(&x);cout<<rk(rt,x-1)+1<<endl;}else if(op==4){read(&x);cout<<kth(rt,x)<<endl;}else if(op==5){read(&x);cout<<kth(rt,rk(rt,x-1))<<endl;}else if(op==6){read(&x);cout<<kth(rt,rk(rt,x)+1)<<endl;}else{cout<<"_"<<endl;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/859561.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

博客园js依赖问题,defer和async

背景 我在博客园后台写的自定义script依赖某个js库,他始终提示xxx not defined. 原因通过对比,不难发现,博客园偷偷改了你的代码,用defer优化了一下,防止js代码的加载阻塞dom渲染。 defer和async script标签可以使用defer或者async属性。 defer: 到dom渲染完毕后执行。 as…

[TSDB] OpenGemini 运维指南

OpenGemini 运维指南 概述 : OpenGemini 运维 Gemix : 官方部署运维一体化工具 启动集群 : gemix cluster start {geminiClusterName} [root@vmw-b ~]# gemix cluster start gemini-test Starting cluster gemini-test... + [ Serial ] - SSHKeySet: privateKey=/root/.gemix/s…

CUDA环境搭建

1.安装CUDA CUDA Toolkit 12.6 Update 3 Downloads | NVIDIA Developer2.安装miniconda Download Now | Anaconda3.安装GPU版的 Pytorch PyTorchpip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124备选手动安装 迅雷下载 https://…

PostgreSQL 初始化配置设置

title: PostgreSQL 初始化配置设置 date: 2024/12/27 updated: 2024/12/27 author: cmdragon excerpt: PostgreSQL是一款广泛应用于企业级应用、数据仓库以及Web应用程序的强大数据库管理系统。在完成数据库的安装后,进行合理而有效的初始配置是确保数据库性能和安全性的关键…

Chrome浏览器不太愿意使用http3/quic怎么办?如何强制让Chrome开启http3/quic

1. 确认你要访问的网站支持h3 一定要有Alt-svc标头2. 配置Chrome启动参数,对指定网站强制开启Http3 右键点击桌面Chrome图标,选择属性,在Chrome.exe后添加启动参数,网站地址替换为你想要开启的网站地址。 chrome.exe --origin-to-force-quic-on=localhost:6001 https://loc…

postgis和postgresql学习

一、两者关系 postgresql本质上还是sql驱动的数据库,和mysql、sqlserver等是性质相同的 postgis本质上是postgresql的插件,可以将postgresql这种关系型数据库改造成空间数据库 二、环境配置 postgresql:https://www.enterprisedb.com/downloads/postgres-postgresql-downloa…

并发编程 - 死锁的产生、排查与解决方案

多线程中死锁因资源争夺形成循环等待,必要条件为互斥、占有并等待、不可剥夺、循环等待。可用VisualStudio等工具排查,解决办法包括顺序加锁、尝试锁、超时机制、避免嵌套使用锁。在多线程编程中,死锁是一种非常常见的问题,稍不留神可能就会产生死锁,今天就和大家分享死锁…

一维数组、多维数组、Array(deepToString sort fill binarySearch)方法2024122620241226

数组20241226 [数组详情](深入理解 Java 数组 - 静默虚空 - 博客园)什么是数组: 数组是相同类型数据的有序集合注意:必须是相同数据数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成其中,每一个数据称作一个数组元素。 每个数组元素可以通过一个下标来访问它…

《计算机组成及汇编语言原理》阅读笔记:p116-p120

《计算机组成及汇编语言原理》学习第 7 天,p116-p120 总结,总计 5 页。 一、技术总结 1.CPU优化 (1)increase overall performance number 例如:16位电脑提升到32位电脑。 (2)multiprocessing One way to make computers more useful is to allow them to run more than on…

波折重重:Linux实时系统Xenomai宕机问题的深度定位过程

本文将带您深入了解一个与之相关的真实事故现场及其问题定位过程,波折重重,其中的xenomai问题定位思路具有一定借鉴意义,希望对你定位xenomai问题有所帮助。目录一 前言二 背景三 原因分析及措施硬件原因应用软件操作系统四 分析定位转机拨云见雾irq计数Schedstatcoreclk现象…

Java面向对象程序设计复习总结

作者:高世栋 学号:202302151071 一、第一章:初识Java与面向对象程序设计Java简介:Java是一种面向对象的程序设计语言,具有跨平台、安全性高、可移植性强等特点。面向对象程序设计概述:面向对象是一种程序设计思想,将现实世界的事物抽象为对象,通过对象之间的交互来完…

[Paper Reading] StegoType: Surface Typing from Egocentric Cameras

目录StegoType: Surface Typing from Egocentric CamerasTL;DRData数据采集设备开环数据收集闭环数据收集数据容错机制OracleMethodInput FeaturesBackboneDataLossExperiment效果可视化总结与思考相关链接Related works中值得深挖的工作资料查询 StegoType: Surface Typing fr…