按层数公式计算满二叉树画布坐标,递归绘制节点和斜边,并跳过被删除的整棵子树。
OJ: luogu
题目 ID: P1185
难度:普及+/提高
标签:二叉树递归模拟python
日期: 2026-07-16 18:17
题意
按固定 ASCII 规则绘制 m 层满二叉树,并删除指定节点、它的所有后代以及与父节点的连线。
思路
画布高度和宽度为:
根位于 (0,width//2)。除叶层外,第 level 层节点行号为 height-3*2^(m-level-1),叶层在最后一行。父子节点横向偏移恰好等于纵向行差,因此连线上的每一步同时改变一行一列。
用满二叉树堆编号表示节点:第 level 层第 index 个节点编号是 2^(level-1)+index-1。先递归把删除节点的整个子树加入集合;绘制时若孩子已删除,就连线和子树都跳过。
Python 知识
- 二维画布必须写成
[[" "]*width for _ in range(height)],保证各行独立。 deleted集合保存堆编号,判断某棵子树是否绘制。- 三元组
(child,direction,slash)把左右孩子的对称绘制合并成同一循环。 - 最后按固定宽度
"".join(row)输出,保留题目要求的行尾空格。 /home/rainboy/mycode/hugo-blog/content/program_language/python/cpp_to_python_pitfalls.md:二维列表独立行。/home/rainboy/mycode/hugo-blog/content/program_language/python/brute_force_validation.md:递归树状态。
代码
python
m, removal_count = map(int, input().split())
height = 3 * (1 << (m - 2))
width = 2 * height - 1
node_row = [0] * (m + 1)
for level in range(1, m):
node_row[level] = height - 3 * (1 << (m - level - 1))
node_row[m] = height - 1
deleted = set()
def remove_subtree(node):
if node >= 1 << m:
return
deleted.add(node)
remove_subtree(node * 2)
remove_subtree(node * 2 + 1)
for _ in range(removal_count):
level, index = map(int, input().split())
remove_subtree((1 << (level - 1)) + index - 1)
canvas = [[" "] * width for _ in range(height)]
def draw(node, level, row, column):
if node in deleted:
return
canvas[row][column] = "o"
if level == m:
return
child_row = node_row[level + 1]
gap = child_row - row
for child, direction, slash in ((node * 2, -1, "/"), (node * 2 + 1, 1, "\\")):
if child in deleted:
continue
for step in range(1, gap):
canvas[row + step][column + direction * step] = slash
draw(child, level + 1, child_row, column + direction * gap)
draw(1, 1, 0, width // 2)
print("\n".join("".join(row) for row in canvas))cpp
/**
* P1185 绘制二叉树
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-27 00:00
* update_at: 2026-07-27 00:00
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXM = 11;
int m, k;
bool del[1 << MAXM]; // 标记被删除的结点(按完全二叉树编号)
char canvas[800][1600]; // 画布(宽 = 2*高-1)
int height, width;
int node_row[MAXM]; // 每层结点的行号
// 递归删除子树
void remove_sub(int u) {
if (u >= (1 << m)) return;
del[u] = true;
remove_sub(u * 2);
remove_sub(u * 2 + 1);
}
// 递归绘制
void draw(int u, int level, int row, int col) {
if (del[u]) return;
canvas[row][col] = 'o';
if (level == m) return;
int child_row = node_row[level + 1];
int gap = child_row - row;
// 左孩子
if (!del[u * 2]) {
for (int step = 1; step < gap; ++step)
canvas[row + step][col - step] = '/';
draw(u * 2, level + 1, child_row, col - gap);
}
// 右孩子
if (!del[u * 2 + 1]) {
for (int step = 1; step < gap; ++step)
canvas[row + step][col + step] = '\\';
draw(u * 2 + 1, level + 1, child_row, col + gap);
}
}
int main() {
scanf("%d%d", &m, &k);
// 计算画布尺寸
height = 3 * (1 << (m - 2));
if (m == 1) height = 1;
width = 2 * height - 1;
// 计算每层结点的行号
for (int i = 1; i < m; ++i)
node_row[i] = height - 3 * (1 << (m - i - 1));
node_row[m] = height - 1;
// 读入并标记删除的结点
for (int i = 1; i <= k; ++i) {
int level, idx;
scanf("%d%d", &level, &idx);
int u = (1 << (level - 1)) + idx - 1;
remove_sub(u);
}
// 初始化画布为空格
for (int i = 0; i < height; ++i)
for (int j = 0; j < width; ++j)
canvas[i][j] = ' ';
// 绘制
draw(1, 1, 0, width / 2);
// 输出
for (int i = 0; i < height; ++i) {
canvas[i][width] = '\0';
printf("%s\n", canvas[i]);
}
return 0;
}复杂度
画布大小为
总结
ASCII 绘树的难点是先推导稳定坐标。用堆编号管理删除、用行差同时决定横向偏移后,左右子树可以统一递归绘制。