绘制二叉树
题目描述
二叉树是一种基本的数据结构,它要么为空,要么由根节点,左子树和右子树组成,同时左子树和右子树也分别是二叉树。
当一颗二叉树高度为(m-1)时,则共有(m)层。除(m)层外,其他各层的结点数都达到最大,且结点节点都在第(m)层时,这就是一个满二叉树。
现在,需要你用程序来绘制一棵二叉树,它由一颗满二叉树去掉若干结点而成。对于一颗满二叉树,我们需要按照以下要求绘制:
1、结点用小写字母"o"表示,对于一个父亲结点,用"/"连接左子树,同样用""连接右子树。
2、定义([i,j)]为位于第(i)行第(j)列的某个字符。若([i,j])为"/",那么([i-1,j+1])与([i+1,j-1])要么为"o",要么为"/"。若([i,j])为"",那么([i-1,j-1])与([i+1,j+1])要么为"o",要么为""。同样,若([i,j])为第(1-m)层的某个节点(即"o"),那么([i+1,j-1])为"/",([i+1,j+1])为""。
3、对于第(m)层节点也就是叶子结点,若两个属于同一个父亲,那么它们之间(由3)个空格隔开,若两个结点相邻但不属于同一个父亲,那么它们之间由(1)个空格隔开。第(m)层左数第(1)个节点之前没有空格。
最后需要在一颗绘制好的满二叉树上删除(n)个结点(包括它的左右子树,以及与父亲的连接),原有的字符用空格替换(ASCII 32
,请注意空格与ASCII 0
的区别(若用记事本打开看起来是一样的,但是评测时会被算作错误答案!))。
输入输出格式
输入格式
第(1)行包含(2)个正整数(m)和(n),为需要绘制的二叉树层数已经从(m)层满二叉树中删除的结点数。
接下来(n)行,每行两个正整数,表示第(i)层第(j)个结点需要被删除($1
输出格式
按照题目要求绘制的二叉树。
输入输出样例
输入样例 #1
2 0
输出样例 #1
o
/
o o
输入样例 #2
4 0
输出样例 #2
o
/
/
/
/
/
o o
/ /
/ /
o o o o
/ / / /
o o o o o o o o
输入样例 #3
4 3
3 2
4 1
3 4
输出样例 #3
o
/
/
/
/
/
o o
/ /
/ /
o o
/
o o o
说明
(30\%)的数据满足:(n=0);
(50\%)的数据满足:(2≤m≤5);
(100\%)的数据满足:(2≤m≤10,0≤n≤10)。
分析
此题是一道比较有质量的题。
一开始写的分治,结果输出非常鬼畜,调了好几天都无果,无奈,参照了题解中的方法直接写的。
我参照的是 KHIN 神的这篇题解。此篇题解几乎和KHIN神的一模一样。
首先,定义 (r_i) (root) 为 (m = i) 时根节点的位置(从 (0) 计数),那么就会有
我们可以考虑简化一下这个式子。首先,找规律能推出,(forall 1 le k le i - 2),有
令 (k = i - 2),则有
也就是说,(forall i ge 2),有 (r_i = 2^i - 2 ^ {i - 2} - 1)。不知道你是否有发现,这个东西恰好是一层中的宽度(也就是该层节点个数)。
那么,每个点到父亲节点的距离是什么呢?不难发现,该距离恰好跳过了该点和父节点间子树的宽度,跟 (r_i) 其实是一个东西。也就是说:
定义 (e_i) (edge)一条 下方有 (i) 个节点的边,该边长度应为:
不考虑删除,每次记录输出时每行边,点的位置,输出后,判断子树方向,若左子树则位置自减,右子树则位置自增。节点判断,更新数组。(本篇题解运用的是滚动数组,滚动输出。)
而一个节点或边的删除与否,我们直接用一个 isErased 数组记录。从上向下逐个扫描,如果父节点被检测到删除或者本身这个节点就被删除了,那么就可以isErased数组记录。输出时,如果这个位置被擦除了,那么直接输出空格即可。
上代码咯。
代码
/*
* @Author: crab-in-the-northeast
* @Date: 2020-08-14 15:00:06
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2020-08-14 22:10:56
*/
//之前写的解法太辣鸡,且死活查不出错,这里参照了类似于KHIN神的方法。
//由神仙xk帮忙debug,并不是抄袭。qwq
//链接:https://www.luogu.com.cn/blog/236807/Solution-luogu-P1185
//顺便 % 一下KHIN神
#include <iostream>
#include <cstdio>
const int maxn = 12;
const int maxm = 12;
bool isErased[maxm][1 << maxm];
int pos[2][1 << maxm];
int main() {
int n, m;
std :: scanf("%d%d", &m, &n);
pos[0][0] = (1 << m) - (1 << m - 2);
for (int i = 0; i < n; ++i) {
int x, y;
std :: scanf("%d%d", &x, &y);
isErased[x - 1][y - 1] = true;
}
for (int i = 1; i < m; ++i)
for (int j = 0; j < 1 << i; ++j)
if(isErased[i - 1][j >> 1])
isErased[i][j] = true;
for (int i = 1; i < pos[0][0]; ++i, putc(' ', stdout));
puts("o");
for (int i = 1; i < m; ++i) {
for (int j = 0; j < 1 << i; ++j)
pos[1][j] = pos[0][j >> 1] + (j & 1 ? 1 : -1);
std :: swap(pos[0], pos[1]);
for (int k = 1; k < std :: max((1 << m - i) - (1 << m - i - 2), 2) ; ++k) {
for (int j = 1, l = 0; l < 1 << i; ++j)
if (j == pos[0][l]) {
putchar(isErased[i][l] ? ' ' : (l & 1 ? '\' : '/'));
pos[0][l] += l & 1 ? 1 : -1;
++l;
} else
putchar(' ');
puts("");
}
for (int j = 1, k = 0; k < 1 << i; ++j)
putchar(j == pos[0][k] && !isErased[i][k++] ? 'o' : ' ');
puts("");
}
return 0;
}