【ybt金牌导航4-5-1】【luogu P3369】普通平衡树(替罪羊树做法)

普通平衡树

题目链接:ybt金牌导航4-5-1 / luogu P3369

题目大意

平衡树模板题,要求维护一些操作。
插入一个数,删除一个数,查询一个数的排名,查询排名一直的数,找前驱后继。

思路

这个其实是模板题,可以有各种解法。
现在在学替罪羊树,就用替罪羊树来做了。

后面有空的话应该会弄一个平衡树合集。

替罪羊其实就是弄一个限度,如果对于一个点,它的某个子树的点的个数占了这个点对应子树点个数的一定量的时候,就需要暴力重构它对应的树,把它的深度弄小。
弄成什么最小呢?没错,就是尽可能弄成每个点两个子树的个数相同的情况。
对于看是否要重构的那个量,我们一般会设 0.60.7

然后就来看看如何暴力重构。
首先,它就要两个子树大小相同,那假设你还原出来数组,那就是每次折半,前面的给左子树,后面的给右子树。
那怎么找到子树呢?我们可以用中序遍历,因为平衡树有二叉搜索树的性质。
然后你再按先序遍历把重构的树里面的点都找出来,然后你每次折半找子树的时候,就拿出一个来作为这个子树的根。

然后其他操作就很暴力,就不多说了,看代码吧。

代码

//替罪羊 #include<cstdio> #define alpha 0.7 using namespace std; struct Tree { int l, r, val, fa, num, sum; }tree[1000001]; struct reTree { int val, num; }retree[100001]; int n, op, x, root, sta[100001]; int maxn, tot, tott; bool check(int x) {//判断是否失衡 if (!tree[x].num) return 0; if (tree[tree[x].l].sum >= (double)(alpha * tree[x].sum)) return 1; if (tree[tree[x].r].sum >= (double)(alpha * tree[x].sum)) return 1; return 0; } void up(int now) {//向上传递值 tree[now].sum = tree[now].num; if (tree[now].l) tree[now].sum += tree[tree[now].l].sum; if (tree[now].r) tree[now].sum += tree[tree[now].r].sum; } void dfs(int x) {//dfs把树拍扁,好重构 sta[++sta[0]] = x;//记录点的编号 if (tree[x].l) dfs(tree[x].l); if (tree[x].num) {//按中序遍历记录值 retree[++tott].val = tree[x].val; retree[tott].num = tree[x].num; } tree[x].sum = 0; if (tree[x].r) dfs(tree[x].r); } int get_new() {//把你拍扁的点取出来 if (sta[0]) { sta[0]--; return sta[sta[0] + 1]; } else return ++tot; } void make_new(int l, int r, int now, int father) {//重构一个最平衡的树 int mid = (l + r) >> 1; tree[now].fa = father; tree[now].num = retree[mid].num; tree[now].val = retree[mid].val; tree[now].sum = 1; if (l <= mid - 1) { tree[now].l = get_new(); make_new(l, mid - 1, tree[now].l, now); } else tree[now].l = 0; if (r >= mid + 1) { tree[now].r = get_new(); make_new(mid + 1, r, tree[now].r, now); } else tree[now].r = 0; up(now); } void rebuild(int x) {//暴力重构树 if (!x) return ; tott = 0; dfs(x);//拍扁,搞粗中序遍历 int now = get_new(); if (x == root) root = now; tree[now].fa = tree[x].fa; int mid = (1 + tott) >> 1;//先把要重构的树的根弄了 if (retree[mid].val < tree[tree[now].fa].val) tree[tree[now].fa].l = now; else tree[tree[now].fa].r = now; make_new(1, tott, now, tree[now].fa); } void insert(int x, bool need_build) {//插入点 if (root == 0) { root = 1; tot = 1; tree[1] = (Tree){0, 0, x, 0, tree[1].num + 1, tree[1].sum + 1}; return ; } int now = root; while (tree[now].sum) {//一直跑跑到插入的位置 if (x == tree[now].val) {//之前出现过,直接加个数 tree[now].num++; tree[now].sum++; return ; } if (!tree[now].l && x < tree[now].val) break;//新的边,要重新建 if (!tree[now].r && x > tree[now].val) break; tree[now].sum++; if (x < tree[now].val) now = tree[now].l; else if (x > tree[now].val) now = tree[now].r; } if (x == tree[now].val) {//之前出现过,直接加个数 tree[now].num++; tree[now].sum++; return ; } tree[now].sum++; int tmp = now;//建立新的点,并且完善值和它父亲的儿子的值 if (x < tree[now].val) now = tree[now].l = ++tot; else if (x > tree[now].val) now = tree[now].r = ++tot; tree[now].num++; tree[now].sum++; tree[now].val = x; if (tmp != now) tree[now].fa = tmp; int first_fix = 0;//判断树是否需要重构 while (now != root) { now = tree[now].fa; if (check(now)) first_fix = now; } if (need_build && first_fix) rebuild(first_fix); } int tree_place(int x) {//查找一个值在树上的位置 int now = root; while (tree[now].val != x && tree[now].sum != 0) { if (x <= tree[now].val) now = tree[now].l; else now = tree[now].r; } return now; } void delete_(int x, int need_build) {//删去一个点 tree[x].num--; tree[x].sum--; int first_fix = 0;//判断是否需要重构 while (x != root) { x = tree[x].fa; tree[x].sum--; if (check(x)) first_fix = x; } if (need_build && first_fix) rebuild(first_fix); } int get_rank(int x) {//得到排名 insert(x, 0);//因为待会会删掉,所以不用重构 int now = tree_place(x); int re = tree[tree[now].l].sum + 1; while (now != root) { if (tree[tree[now].fa].r == now) re += tree[tree[tree[now].fa].l].sum + tree[tree[now].fa].num; now = tree[now].fa; } delete_(tree_place(x), 0); return re; } int get_num(int x) {//得到排名对于的值 int now = root; while (x) { if (x <= tree[tree[now].l].sum) now = tree[now].l; else if (x <= tree[tree[now].l].sum + tree[now].num) return tree[now].val; else { x -= tree[tree[now].l].sum + tree[now].num; now = tree[now].r; } } return tree[now].fa; } int pre(int x) {//前驱 insert(x, 0); int need_rank = get_rank(x) - 1; delete_(tree_place(x), 0); return get_num(need_rank); } int suc(int x) {//后继 insert(x + 1, 0); int need_rank = get_rank(x + 1); delete_(tree_place(x + 1), 0); return get_num(need_rank); } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d %d", &op, &x); if (op == 1) { insert(x, 1); continue; } if (op == 2) { delete_(tree_place(x), 1); continue; } if (op == 3) { printf("%d\n", get_rank(x)); continue; } if (op == 4) { printf("%d\n", get_num(x)); continue; } if (op == 5) { printf("%d\n", pre(x)); continue; } if (op == 6) { printf("%d\n", suc(x)); continue; } } return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_4-5-1.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(24)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示