题目链接P1185 绘制二叉树

题意概述

  根据规则绘制一棵被删去部分节点的满二叉树。节点用 \(o\) 表示,树枝用/\表示。每一层树枝长度会变化,以满足叶子结点有如下特定:

  • 相邻叶子节点是兄弟节点(同一个父亲)时,间隔 \(3\) 个空格。
  • 相邻叶子节点不是兄弟节点,之间隔一个空格。

  一棵层数为 \(4\) 的满二叉树长这样(可能会出现因为字符宽度不一而出现偏移):

           o
/ \
/ \
/ \
/ \
/ \
o o
/ \ / \
/ \ / \
o o o o
/ \ / \ / \ / \
o o o o o o o o

  删除节点的输入格式为:删除第 \(i\) 层从左往右数的第 \(j\) 个节点。注意删除时,把原有的字符用空格替换,结果是要打印空格的

分析

  又是一道画图模拟题,需要耐心分析。我采取的是找规律的方法,代码可能长,但是应该比较容易理解吧 \(QAQ\) 。

  先看我们得维护什么信息才能实现初始化满二叉树和删点两个操作。初始化的方法有挺多,可以先铺好叶子结点,往上递归建树,也可以从根节点往下建树。但是有个问题是我们并不知道叶子结点到根节点的垂直距离,也不知道根节点的坐标。这时候我们就得找树枝的规律了。建好树后我们要删点,但是输入点的方式不能直接确定点的坐标,得找同一层节点分布的规律。为了后续讨论方便,我们约定叶子节点为第一层,根节点在第 \(m\) 层

树枝的规律

  打表加看图硬分析(这里的树枝长定义为连接第 \(i\) 层节点与第 \(i+1\) 层节点的树枝长,表格有些许错位):

层数 \(1\) \(2\) \(3\) \(4\) \(5\)
树枝长 \(len\) \(1\) \(2\) \(5\) \(11\) \(23\)
规律 \(1\) \(1+(2-1)\) \((1+2)+(3-1)\) \((1+2+5)+(4-1)\) \((1+2+5+11)+(5-1)\)

  可以看出,对于第 \(i(2 \leq i )\) 层的树枝长,其实是等于前 \(i-1\) 层树枝的长度之和与 \(i-1\) 的和的。看图更容易发现这一规律:



  这里第 \(3\) 层的树枝和前两层的树枝和节点有一一对应的关系(红色实线),可以看出长度恰好就是前两层节点数2加上前两层的树枝长度,\(O(n)\) 递推可以得到树枝长度数组,记为 \(len\) 。

同层节点规律

  观察可知除了第一层外的其他层的同层相邻节点距离是一定的。所以确定每一层第一个节点的位置就可以推出其他节点的位置了。

  让第一层第一个节点水平位置为 \(1\) 。再次观察前面的图,可以发现第 \(i\) 层第一个节点的水平位置其实就是 \(len_i+1\) 。所以根据前面推出来的树枝长数组可以推出。竖直位置就得从根节点(也就是第 \(m\) 层)往下推了,根节点竖直位置为 \(1\) ,第 \(i\) 层竖直位置就其实就是 \(=\) 第 \(i+1\) 层竖直位置 \(+\) 第 \(i\) 层的树枝长度 \(+1\) ,也是比较明显的。我代码中将两个方向的位置分别用 \(pos\) 和 \(h\) 表示了。以下是初始化函数和一些数组定义:

const int N = 3100;
int len[20],m,n,pos[20],h[20];
char a[N][N]; //满二叉树数组,注意开大一点
void prepare(){
int sum = 1; //记录树枝长的前缀和
len[1] = 1;pos[1] = 1; //第一层树枝长为1,第一个节点水平位置为1
FOR(i,2,m) {
len[i] = sum + i-1; //递推式子
sum += len[i];
pos[i] = len[i] + 1;//顺便得到第i层第一个节点的水平位置
}
h[m] = 1;
for(int i = m-1; i ;i --) h[i] = h[i+1]+len[i]+1;//得到第i层的竖直位置
memset(a,' ',sizeof(a)); //全都铺满空格
}

  第一层节点的分布已在题目中确定了,相邻节点是兄弟就隔 \(3\) 个,不是隔 \(1\) 个,因为与其他层分布不同,是要特判的。其他层结点间距也是很好找到规律的,就是 \(2 \times len_i+1\) 。至此,我们这棵树的信息基本完备了,下面就是比较轻松的绘制和删点了。

绘制和删点

  这两个操作都是递归进行的。

  因为我们已经知道了每一层的树枝长度,所以我们可以从根节点开始建树,递归左右子树即可。注意我们定义的树枝长度为连接第 \(i\) 层节点与第 \(i+1\) 层节点的树枝长度。代码采用了前序遍历的方式:

void draw(int x,int y,int depth){
a[x][y] = 'o'; //画节点
if(depth == 1) return; //到叶子节点了,返回
//开始画树枝,lx,ly定位左树枝,rx,ry定位右树枝
int lx = x+1,ly = y-1,rx = x+1,ry = y+1;
FOR(i,1,len[depth-1]){//注意画的树枝长度为下一层的树枝长度
a[lx][ly] = '/';
a[rx][ry] = '\\';
lx = lx+1,ly = ly-1,rx = rx+1,ry = ry+1;
}
draw(lx,ly,depth-1); //画下一层节点
draw(rx,ry,depth-1);
}

  删点比较暴力,注意删点要同时删除与父亲节点的联系和与孩子节点的联系:

void destroy(int x,int y){
a[x][y] = ' '; //将该点置为空格
if(a[x-1][y-1] == '\\') destroy(x-1,y-1); //左上角
if(a[x-1][y+1] == '/') destroy(x-1,y+1); //右上角
if(a[x+1][y-1] == '/' || a[x+1][y-1] == 'o') destroy(x+1,y-1); //左下角,因为往下还要删除孩子节点,要多一个判断
if(a[x+1][y+1] == '\\'|| a[x+1][y+1] == 'o') destroy(x+1,y+1); //右下角同理
}

一些可能阻止你AC的坑

  • 数组大小要开大一点。满二叉树最大层数为 \(10\) ,叶子结点的竖直为置最大为 \(768\),该层宽度为 \(3072\) 。所以数组大小应至少开到 \(769*3073\) 。否则可能出现 \(\mathbf{Too~ long~ on~ line~ 1}.\) 或者直接 \(\mathbf{RE}\) 等错误。
  • 数组定义比较多,要用一些比较清晰的变量名,并且时刻记得它们的意义。
  • 第 \(10\) 个点有点玄学。如果用快读会 \(\mathbf{TLE}\) 掉,因为数据量小,全都用 \(\mathbf{cin}\) 就可以过了。

    \(Code:\)
#include <bits/stdc++.h>
#define FOR(i,a,b) for(int i = a;i <= b;i++)
using namespace std;
const int N = 3100;
int len[20],m,n,pos[20],h[20];
char a[N][N]; //满二叉树数组,注意开大一点
int read(){int sum = 0,fu = 1;char ch = getchar();while(!isdigit(ch)){if(ch == '-')fu = -1;ch = getchar();}while (isdigit(ch)){sum=(sum<<1)+(sum<<3)+(ch^48);ch = getchar();}return sum*fu;}
//预处理
void prepare(){
int sum = 1; //记录树枝长的前缀和
len[1] = 1;pos[1] = 1; //第一层树枝长为1,第一个节点水平位置为1
FOR(i,2,m) {
len[i] = sum + i-1; //递推式子
sum += len[i];
pos[i] = len[i] + 1;//顺便得到第i层第一个节点的水平位置
}
h[m] = 1;
for(int i = m-1; i ;i --) h[i] = h[i+1]+len[i]+1;//得到第i层的竖直位置
memset(a,' ',sizeof(a)); //全都铺满空格
} //绘制
void draw(int x,int y,int depth){
a[x][y] = 'o'; //画节点
if(depth == 1) return; //到叶子节点了,返回
//开始画树枝,lx,ly定位左树枝,rx,ry定位右树枝
int lx = x+1,ly = y-1,rx = x+1,ry = y+1;
FOR(i,1,len[depth-1]){ //注意画的树枝长度为下一层的树枝长度
a[lx][ly] = '/';
a[rx][ry] = '\\';
lx = lx+1,ly = ly-1,rx = rx+1,ry = ry+1;
}
draw(lx,ly,depth-1); //画下一层节点
draw(rx,ry,depth-1);
} //删点
void destroy(int x,int y){
a[x][y] = ' '; //将该点置为空格
if(a[x-1][y-1] == '\\') destroy(x-1,y-1); //左上角
if(a[x-1][y+1] == '/') destroy(x-1,y+1); //右上角
if(a[x+1][y-1] == '/' || a[x+1][y-1] == 'o') destroy(x+1,y-1); //左下角,因为往下还要删除孩子节点,要多一个判断
if(a[x+1][y+1] == '\\'|| a[x+1][y+1] == 'o') destroy(x+1,y+1); //右下角同理
} //打印
void print(){
int height = h[1]; //第一层的竖直位置
int width = 6 * (1<<(m-1)); //第一层的宽度(最宽)
FOR(i,1,height){
FOR(j,1,width)
printf("%c",a[i][j]);
printf("\n");
}
} signed main(){
m = read();n = read();
prepare();
draw(1,pos[m],m); //(1,pos[m])为根节点坐标,位于第m层
while(n--){
int i = read(),j = read();
if(i > 10) continue;
int x = h[m+1-i],y; //因为层的定义与题目不同,得转化一下
//分第一层和其他层两种情况计算水平位置y
if(i == m){
if(j & 1) y = pos[1] + j/2*6;
else y = pos[1] + j/2*6 - 2;
}
else y = pos[m+1-i] + (j-1)* (2 * len[m+1-i] + 2); //可以手推
destroy(x,y);
}
print();
return 0;
}

  如果你想练习一下类似的画图题,以下两题可以做做看:

模拟画图题P1185 绘制二叉树的更多相关文章

  1. HDOJ 2317. Nasty Hacks 模拟水题

    Nasty Hacks Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Tota ...

  2. php算法题---对称的二叉树

    php算法题---对称的二叉树 一.总结 一句话总结: 可以在isSymmetrical()的基础上再加一个函数comRoot,函数comRoot来做树的递归判断 /*思路:首先根节点以及其左右子树, ...

  3. POJ 2014:Flow Layout 模拟水题

    Flow Layout Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 3091   Accepted: 2148 Descr ...

  4. Codeforces Round #249 (Div. 2) C题,模拟画图 ----未解决!

    http://codeforces.com/contest/435/problem/C

  5. wrf模拟的domain图绘制

    wrf模拟的区域绘制,domain图,利用python的cartopy库绘制模拟区域 参考Liang Chen的draw_wrf_domian.py这个代码, 出处python画wrf模式的模拟区域 ...

  6. 剑指offer——python【第60题】把二叉树打印成多行

    题目描述 从上到下按层打印二叉树,同一层结点从左至右输出.每一层输出一行.#类似于二维列表[[1,2],[4,5]] 解题思路 其实这倒题和其他类似的题有所区别,这里是分层打印,把每层的节点值放在同一 ...

  7. (大模拟紫题) Luogu P1953 易语言

    原题链接:P1953 易语言 (我最近怎么总在做大模拟大搜索题) 分别处理两种情况. 如果只有一个1或0 直接设一个cnt为这个值,每次输入一个新名字之后把数字替换成cnt,最后cnt++即可. 注意 ...

  8. PMP模拟错题总结

    本打算15天完成第二轮复习的,结果项目太忙,拖成了25天.第二轮主要以小绿书为主,就是如下这本 怎么说呢,题目偏向于考ITTO的内容,情景题比较少.可以使用“管理圈”APP作为补充 1.敏感性分析的结 ...

  9. Codeforces Round #350 (Div. 2) F. Restore a Number 模拟构造题

    F. Restore a Number   Vasya decided to pass a very large integer n to Kate. First, he wrote that num ...

随机推荐

  1. Python List cmp()方法

    描述 cmp() 方法用于比较两个列表的元素.高佣联盟 www.cgewang.com 语法 cmp()方法语法: cmp(list1, list2) 参数 list1 -- 比较的列表. list2 ...

  2. PHP link() 函数

    定义和用法 link() 函数创建一个从指定名称连接的现存目标文件开始的硬连接. 如果成功,该函数返回 TRUE.如果失败,则返回 FALSE. 语法 link(target,link) 参数 描述 ...

  3. PHP tempnam() 函数

    定义和用法 tempnam() 函数在指定的目录中创建一个具有唯一文件名的临时文件. 该函数返回新的临时文件名,如果失败则返回 FALSE. 语法 tempnam(dir,prefix) 参数 描述 ...

  4. 4.2 省选模拟赛 旅行路线 广义SAM

    \(n\leq 100000\) 题目上求出 多少条本质不同的路线. 首先定义了 相似的城市为度数相同的城市. 还定义了两条路线相同当且仅当长度相同 且对应位置的城市都是相似的. 考虑这张图的形态 n ...

  5. 学习使用CompletableFuture

    CompletableFuture 一.前言 1.JDK5的异步处理方式 2.JDK8的异步处理方式 二.学习CompletableFuture 1.结果获取方式 2.创建CompletableFut ...

  6. Spring与Mybatis整合占位符无法解析问题

    问题:写了一个新的dao接口,进行单元测试时提示: Initialization of bean failed; nested exception is org.springframework.bea ...

  7. 【PA2014】Bohater 题解(贪心)

    前言:一道经典贪心题. -------------------------- 题目链接 题目大意:你有$z$滴血,要打$n$只怪.打第$i$只怪扣$d_i$滴血,回$a_i$滴血.问是否存在一种能够通 ...

  8. 数据结构中的树(二叉树、二叉搜索树、AVL树)

    数据结构动图展示网站 树的概念 树(英语:tree)是一种抽象数据类型(ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合.它是由n(n>=1)个有限节点组成一个具有 ...

  9. Python最全pdf学习书籍资料分享

    本人学习Python两年时间,期间统计了一些比较好的学习资料 1.基础资料  下载地址: 链接:https://pan.baidu.com/s/1sjtyYayBbQLsrUdaXWmzkg提取码:1 ...

  10. 新老版本vue-cli的安装及创建项目等方式的比较

    vue-cli 3.0 正式版于2018年8月发布,截至到2020年08月05日版本已经更新到4.4.6.Vue CLI 的包名称由 vue-cli 改成了 @vue/cli,目前网上很多的Vue项目 ...