备赛蓝桥杯:红黑树问题的核心规律(行号 n 没用?Python 实战验证)
题目重述
小蓝构造了一棵无限深度的特殊红黑树,规则如下:
| 节点颜色 | 左子节点 | 右子节点 |
|---|---|---|
| 红色 ® | 红色 ® | 黑色 (B) |
| 黑色 (B) | 黑色 (B) | 红色 ® |
- 根节点 :第1行第1个,固定为 RED
- 第n行 :共有 2n −1 个节点(1 ≤ k ≤ 2n−1)
- 编号规则 :每行从左到右编号为 1, 2, ..., 2n−1
树的前4层可视化(括号内为 (行, 位置)):
R(1,1)
/ \
R(2,1) B(2,2)
/ \ / \
R(3,1) B(3,2) B(3,3) R(3,4)
/ \ / \ / \ / \
R B B R B R R B ... (第4行)
输入格式
第一行:整数 m (查询次数,1≤m ≤105)
接下来 m 行:每行两个整数 n ,k (1≤n ≤30, 1≤k ≤2n−1)
输出格式
对每个查询,输出 RED 或 BLACK
深度规律挖掘(附路径推导)
核心发现:颜色 = Thue-Morse 序列
将 k-1 转为二进制 ,统计其中 1 的个数的奇偶性:
- 偶数个 1 → RED
- 奇数个 1 → BLACK
验证示例
| 查询 (n,k) | k-1 | 二进制 | 1的个数 | 奇偶 | 颜色 |
|---|---|---|---|---|---|
| (1,1) | 0 | 0 |
0 | 偶 | RED |
| (2,2) | 1 | 1 |
1 | 奇 | BLACK |
| (3,3) | 2 | 10 |
1 | 奇 | BLACK |
| (3,4) | 3 | 11 |
2 | 偶 | RED |
| (4,6) | 5 | 101 |
2 | 偶 | RED |
严谨数学证明(归纳法)
设 f (k ) 表示第 n 行第 k 个节点的颜色(0=RED, 1=BLACK)
命题 :f (k )=popcount(k−1)mod2
- 基例 :k =1(根),k −1=0,popcount=0,f(1)=0(RED)✓
- 归纳步 :假设第 n 行成立,考察第 n +1 行:
- 左子节点:k ′=2k −1 → k ′−1=2(k−1) → 二进制末尾补0 → popcount 不变 → 颜色与父节点相同 ✓
- 右子节点:k ′=2k → k ′−1=2(k −1)+1 → 二进制末尾补1 → popcount+1 → 颜色翻转 ✓
(符合"红→右=黑"、"黑→右=红"的规则)
结论 :颜色仅取决于 k −1 的二进制中 1 的个数奇偶性,与行号 *n* 无关 (但输入需保留 n 用于验证 k 合法性)
四种解法实现(含递归理解版)
方法1:递归模拟(教学理解,不推荐实战)
python
def get_color_recursive(n, k):
"""递归模拟路径(深度=n,仅用于理解规律)"""
if n == 1: # 根节点
return "RED"
parent_k = (k + 1) // 2 # 父节点位置
is_left = (k % 2 == 1) # 当前是父节点的左子?
parent_color = get_color_recursive(n-1, parent_k)
if parent_color == "RED":
return "RED" if is_left else "BLACK"
else:
return "BLACK" if is_left else "RED"
- 时间 :O (n ) per query | 空间 :O (n)(递归栈)
- 适用 :n ≤30 时可运行,但 m 大时超时风险高
方法2:内置函数法(推荐)
python
def get_color_builtin(n, k):
"""利用Python内置函数:简洁高效"""
# bin(k-1) 返回 '0b101',count('1') 统计1的个数
return "RED" if bin(k - 1).count('1') % 2 == 0 else "BLACK"
- 时间 :O (logk)(实际极快,C层实现)
- 优势:代码最简、可读性高、AC首选
方法3:Brian Kernighan 位运算(极致优化)
python
def get_color_bitwise(n, k):
"""清除最低位1:循环次数=1的个数"""
x = k - 1
parity = 0
while x:
parity ^= 1 # 每遇一个1翻转奇偶标志
x &= x - 1 # Brian Kernighan: 清除最低位1
return "RED" if parity == 0 else "BLACK"
- 时间 :O (popcount(k−1)),平均优于方法2
- 场景 :对性能极致要求(如 m=106)
方法4:查表法(超大查询量优化)
python
# 预处理0~65535的奇偶性(16位)
PARITY_TABLE = [bin(i).count('1') % 2 for i in range(65536)]
def get_color_lookup(n, k):
x = k - 1
# 分高低16位查表异或
p = PARITY_TABLE[x & 0xFFFF] ^ PARITY_TABLE[(x >> 16) & 0xFFFF]
return "RED" if p == 0 else "BLACK"
- 时间 :O(1) per query(常数极小)
- 适用 :m>105 且需极致速度
多维度解法对比
| 方法 | 时间复杂度 | 空间 | 代码长度 | 适用场景 | 推荐指数 |
|---|---|---|---|---|---|
| 递归模拟 | O (n) | O (n) | 15行 | 教学理解 | ⭐ |
| 内置函数 | O (logk) | O(1) | 1行 | 通用首选 | ⭐⭐⭐⭐⭐ |
| 位运算优化 | O(pop) | O(1) | 5行 | 高性能需求 | ⭐⭐⭐⭐ |
| 查表法 | O(1) | O(1) | 8行 | 超大查询量 (m>105) | ⭐⭐⭐ |
蓝桥杯实战建议 :直接使用 方法2(内置函数),简洁可靠,Python选手首选!
全面测试用例(含边界与陷阱)
基础验证
输入:
5
1 1
2 1
2 2
3 3
4 6
输出:
RED
RED
BLACK
BLACK
RED
边界测试
| 用例 | 说明 | 预期 |
|---|---|---|
30 1 |
最小k(首节点) | RED(k-1=0) |
30 536870912 |
k=229(末节点) | BLACK(229−1 有29个1) |
15 16384 |
k=214 | BLACK(214−1 有14个1→偶?错!214−1 二进制14个1→偶→RED?验证:214=16384, k−1=16383=111...111(14个1) → 14偶 → RED) |
5 10 |
随机中间值 | RED(9=1001₂ → 2个1) |
常见错误输入(需防御性编程)
python
# 错误1:忘记k-1(直接用k)
bin(1).count('1') % 2 = 1 → 误判(1,1)为BLACK ❌
# 错误2:混淆行列
# 误以为需用n计算,实际n仅用于验证k范围(题目保证合法可省略)
# 错误3:输入处理遗漏
# 未处理多余空格/换行 → 使用sys.stdin.read().split()最稳妥
拓展知识(提升算法视野)
与经典序列的关联
- Thue-Morse 序列 :
t**i =popcount(i )mod2,本题即 t**k −1
→ 应用于避免重复模式、分形几何、博弈论 - 格雷码 (Gray Code) :
相邻数仅1位变化,本题路径编码本质是标准二进制 - 汉明重量 (Hamming Weight) :
即二进制中1的个数,本题核心计算目标
思维升华
- 树结构 → 路径编码:将树遍历转化为二进制路径
- 状态转移 → 奇偶性:颜色翻转等价于模2加法
- 数学抽象能力:从具体规则提炼出通用数学模型
- 位运算实战价值:Brian Kernighan 算法在嵌入式/高性能场景广泛应用
推荐延伸阅读
- Thue-Morse Sequence (Wikipedia)
- 《具体数学》第1章:递归与和式(奇偶性分析)
- LeetCode 191. Number of 1 Bits(位运算经典题)
总结与备赛建议
| 关键点 | 说明 |
|---|---|
| 核心规律 | 颜色 = (k-1).bit_count() % 2(Python 3.8+ 可直接用 .bit_count()) |
| 行号n作用 | 题目输入需保留,但计算中完全不需要(规律与深度无关) |
| 最优写法 | print("RED" if (k-1).bit_count() % 2 == 0 else "BLACK") |
| 避坑指南 | ① 必须 k-1 ② 注意1-based索引 ③ 无需验证k范围(题目保证合法) |
| 蓝桥技巧 | Python 3.10+ 支持 (k-1).bit_count(),比 bin().count() 更快! |
最后叮嘱 :
遇到树结构题,先画小规模示例 → 寻找编号/路径规律 → 尝试数学归纳 → 转化为位运算/数论问题。
规律发现能力 > 代码实现能力,这才是算法竞赛的精髓!