题解——红黑树

进制运算-红黑树

题目描述

红黑树是一类特殊的二叉搜索树,其中每个结点被染成红色或黑色。若将二叉搜索树结点中的空指针看作是指向一个空结点,则称这类空结点为二叉搜索树的前端结点。并规定所有前端结点的高度为 1

一棵红黑树是满足下面“红黑性质”的染色二叉搜索树:

  1. 每个结点被染成红色或黑色;
  2. 每个前端结点为黑色结点;
  3. 任一红结点的子结点均为黑结点;
  4. 在从任一结点到其子孙前端结点的所有路径上具有相同的黑结点数。

从红黑树中任一结点 x 出发(不包括结点 x),到达一个前端结点的任意一条路径上的黑结点个数称为结点 x 的黑高度,记作 bh(x) 。红黑树的黑高度定义为其根结点的黑高度。

给定正整数 N,试设计一个算法,计算出在所有含有 N 个结点的红黑树中,红色内结点个数的最小值和最大值。

输入格式

输入一个数字 N

输出格式

输出第一行为红色内节点的个数最小值;第二行为红色内节点的个数最大值。

样例 #1

样例输入 #1

8

样例输出 #1

1
4

提示

N5000

DP解法

f[i,j,0/1]表示在有i个节点,黑高度为j,当前树是红/黑根树的最小值(最大值同理)

则有:

f[i,j,0]=min1k<i1min1j2log2n{f[ik1,j,1]+f[k,j,1]}+1

f[i,j,1]=min1k<i1min1j2log2n{f[ik1,j1,0/1]+f[k,j1,0/1]}

最大值同理,只是将min换为max

贪心解法

引理1:任意含有n个节点的树必定含有n+1个前端节点

证明 首先若树是一条链显然成立。我们考虑将这个树在某个节点处旋转过来,也即将链中某个节点作为新的节点,两边的链作为儿子节点,此时这个节点原来有的一个前端节点没了,但是旋转前链顶在旋转后便拥有了2个前端节点,以此类推归纳可证

那么这道题的贪心思路就可以开始启迪我们了

我们可以看作用n+1个节点,两两合并成新的节点并拼在一起,最终合并出根节点,此时就启发我们探索合并过程中所产生的红节点的最少与最多的策略了

因为节点两两合并,我们考虑将黑点的合并抽象出来,考虑黑点合并的基本模型

  1. 两两合并成一黑
  2. 三个合并成一红两黑
  3. 四个合并成两红一黑


因为这三种情况包括了黑点产生和红点产生的所有情况,所以说我们只需要这三种情况就可以拼出其他情况(因为红黑树的性质4,会导致最底层一定是这三种之一,层层上推即可得证)

那么分析一下,很明显情况3用来求最大值,情况1用来求最小值

最小值

需要注意的是,为了黑高度的合法,我们必须一层一层的往上构造
那么先说求最小值,我们可以先用黑点数量除以二就等同于整体向上用情况1构造一层,前提条件是黑点数量是偶数,当黑点数量是奇数的时候,先拿出两个合并成一个红点,因为这样不会影响黑高,并且会使得点的数量重新变为偶数,再除以2,直到黑点数量为1

通过这个想法,我们可以进行优化,会发现,我们实际上的红点个数就是在不断除以二的过程中除到奇数的情况(1除外),所以一个节点数量为n的红黑树,它的最小红节点数量就是n+1在2进制状态下1的个数减1

最大值

然后再来看最大值,想想,最优策略应该是什么,应该是一层层的用4个节点往上构造2层,不够4个节点的,如果只有1个,需要拿出1个4,构成"2+3"。否则就是"3"/"2"

那么此时我们应该对黑节点数量的情况分类讨论,假设现在有k个黑节点数量

  1. kmod40,此时我们选择整体向上构造两层,那么答案累加上k2,黑点数量变为k4
  2. kmod41,此时我们选择构成“2+3”此时答案应该是累加上:k32,此时k变成k54+2=k+34
  3. kmod42,此时我们选择构造"2",答案应该是累加上k22,然后k变为k24+1=k+24
  4. kmod43,此时我们选择构造"3",答案应该是累加上k32+1=k12,然后k变成k34+1=k+14

特别的,当k=2时,直接令k=1,答案加上1,因为两黑自动合成红

一些在操作过程中可能比较迷惑的地方

  1. 随时,我们留下的k个点都是黑点,红点是我们选择以上三种基本构造模型的时候搞出来的

  2. 我们合并过程中并不是强制要求剩下的k个点一定在同一深度

  3. 可能有人会说,如果我们把kmod41的情况那个1直接不动会更好,因为它在同样贡献的情况下还多出来了黑色节点,这个错误的原因是我们必须使用上面三个模型进行合并,否则最大深度差会大于1,导致这棵树建立出来有问题

注意:代码中/2,/4必须使用">>"不能用除号,原因是向下取整和向零取整的区别,用/的只有60分
下面贴上代码

#include<iostream>
#include<cstdio>
using namespace std;
int k,n,mn,mx;
int main(){
	scanf("%d",&n);
	k=n+1;
	while(k>1){
		if(k&1)mn++;
		k>>=1;
	}
	k=n+1;
	while(k>1){
		if(k==2)mx++,k--;
		else if(k%4==0){
			mx+=k>>1;
			k>>=2;
		}
		else if(k%4==1){
			mx+=(k-3)>>1;
			k=(k+3)>>2;
		}
		else if(k%4==2){
			mx+=(k-2)>>1;
			k=(k+2)>>2;
		}
		else {
			mx+=(k-1)>>1;
			k=(k+1)>>2;
		}
	}
	printf("%d\n%d",mn,mx);
}
posted @   spdarkle  阅读(107)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示