题目链接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. 用大白话讲Java动态代理的原理

    动态代理是什么 首先说下代理模式,代理模式是常见的一种java设计模式,特征是代理类与委托类实现了同样的接口,代理类主要负责为委托类预处理.过滤.转发,以及事后处理等.代理类与委托类之间通常会存在关联 ...

  2. win10在html上运行java的applet程序

    Applet是采用Java编程语言编写的小应用程序,该程序可以包含在 HTML(标准通用标记语言的一个应用)页中,与在页中包含图像的方式大致相同. 含有Applet的网页的HTML文件代码中部带有 和 ...

  3. InvalidProgramException: Specifying keys via field positions is only valid for tuple data types

    Run Flink实例时,出现如下错误: 原因:Java程序引用了Scala的Tuple2类 遇到的坑,记录下来!

  4. Asp.Net项目发布 到 IIS、 Core3.1 发布到 IIS CentOS8.x

    摘要:发布项目到IIS或者.Net Core 项目发布到IIS服务器或者CentOS记录一下,后面忘了又来看看. 1.服务器安装IIS 1.1.不管你是本地的电脑还是网上购买的服务器,只要是能通过远程 ...

  5. day3. 六大标准数据类型的类型转换

    一.强制类型转换Number 1.int  强制转换成整型 var1 = 13 var2 = 13.789 var3 = True var4 = 5-7j var5 = "" va ...

  6. 实验10—— java读取歌词文件内容动画输出

    1.Read.java package cn.tedu.demo; import java.io.BufferedReader; import java.io.File; import java.io ...

  7. CSS可见格式化模型

    1.盒模型 1.1 盒子大小 盒模型描述了元素如何显示,以及如何相互作用.相互影响. 页面中的所有元素都被看作一个矩形盒子,这个盒子包含元素的内容.内边距.边框和外边距. 给元素应用的背景会作用于元素 ...

  8. C#开发笔记之01-为什么开源框架会大量的使用protected virtual?

    C#开发笔记概述 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/957 访问. 我们在很多开源框架中会经常看到prote ...

  9. 用python爬虫监控CSDN博客阅读量

    作为一个博客新人,对自己博客的访问量也是很在意的,刚好在学python爬虫,所以正好利用一下,写一个python程序来监控博客文章访问量 效果 代码会自动爬取文章列表,并且获取标题和访问量,写入exc ...

  10. linux下免密登录配置

    1.首先大家先开三台虚拟机 2.回到首层. 2.1:编辑文件:    vim /etc/ssh/sshd_config 3:在master的linux上生成ssh密钥: ssh-keygen -t r ...