[luogu p1185] 绘制二叉树

传送门

绘制二叉树

题目描述

二叉树是一种基本的数据结构,它要么为空,要么由根节点,左子树和右子树组成,同时左子树和右子树也分别是二叉树。

当一颗二叉树高度为\(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\) 计数),那么就会有

\[r_i = \begin{cases} 0 & i = 1 \\ 2 & i = 2 \\ 2r_{i - 1} + 1 & i > 2\end{cases} \]

我们可以考虑简化一下这个式子。首先,找规律能推出,\(\forall 1 \le k \le i - 2\),有

\[r_i = 2^kr_{i - k} + 2 ^ k - 1 \]

\(k = i - 2\),则有

\[\begin{aligned}r_i &= 2^{i - 2}r_2 + 2 ^ {i - 2} - 1 \\ &= 2 ^ {i - 2} \times 2 + 2 ^ {i - 2} - 1 \\ &= 2 ^ {i - 1} + 2 ^ { i - 2} - 1 \\ &= 2^i - 2 ^ {i - 2} - 1\end{aligned} \]

也就是说,\(\forall i \ge 2\),有 \(r_i = 2^i - 2 ^ {i - 2} - 1\)。不知道你是否有发现,这个东西恰好是一层中的宽度(也就是该层节点个数)。

那么,每个点到父亲节点的距离是什么呢?不难发现,该距离恰好跳过了该点和父节点间子树的宽度,跟 \(r_i\) 其实是一个东西。也就是说:

定义 \(e_i\) (edge)一条 下方有 \(i\) 个节点的边,该边长度应为:

\[e_i = \begin{cases} 1 & i = 1 \\ r_i & i > 1 \end{cases} \]

不考虑删除,每次记录输出时每行边,点的位置,输出后,判断子树方向,若左子树则位置自减,右子树则位置自增。节点判断,更新数组。(本篇题解运用的是滚动数组,滚动输出。)

而一个节点或边的删除与否,我们直接用一个 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;
}

评测记录

评测记录

posted @ 2020-08-17 00:37  东北小蟹蟹  阅读(160)  评论(0编辑  收藏  举报