题解 P1185 【绘制二叉树】
在博客中观看
题意概述
根据规则绘制一棵被删去部分节点的满二叉树。节点用 /\表示。每一层树枝长度会变化,以满足叶子结点有如下特定:
- 相邻叶子节点是兄弟节点(同一个父亲)时,间隔
3 个空格。 - 相邻叶子节点不是兄弟节点,之间隔一个空格。
一棵层数为
o
/ \
/ \
/ \
/ \
/ \
o o
/ \ / \
/ \ / \
o o o o
/ \ / \ / \ / \
o o o o o o o o
删除节点的输入格式为:删除第
分析
又是一道画图模拟题,需要耐心分析。我采取的是找规律的方法,代码可能长,但是应该比较容易理解吧
先看我们得维护什么信息才能实现初始化满二叉树和删点两个操作。初始化的方法有挺多,可以先铺好叶子结点,往上递归建树,也可以从根节点往下建树。但是有个问题是我们并不知道叶子结点到根节点的垂直距离,也不知道根节点的坐标。这时候我们就得找树枝的规律了。建好树后我们要删点,但是输入点的方式不能直接确定点的坐标,得找同一层节点分布的规律。为了后续讨论方便,我们约定叶子节点为第一层,根节点在第
树枝的规律
打表加看图硬分析(这里的树枝长定义为连接第
| 层数 | |||||
|---|---|---|---|---|---|
| 树枝长 |
|||||
| 规律 |
表格如果显示不全可以到我的博客上看
可以看出,对于第
这里第
同层节点规律
观察可知除了第一层外的其他层的同层相邻节点距离是一定的。所以确定每一层第一个节点的位置就可以推出其他节点的位置了。
让第一层第一个节点水平位置为
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)); //全都铺满空格
}
第一层节点的分布已在题目中确定了,相邻节点是兄弟就隔
绘制和删点
这两个操作都是递归进行的。
因为我们已经知道了每一层的树枝长度,所以我们可以从根节点开始建树,递归左右子树即可。注意我们定义的树枝长度为连接第
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/26; else y = pos[1] + j/26 - 2; } else y = pos[m+1-i] + (j-1) (2 len[m+1-i] + 2); //可以手推 destroy(x,y); } print(); return 0; }
  如果你想练习一下类似的画图题,以下两题可以做做看:
+ [P1498 南蛮图腾](https://www.luogu.com.cn/problem/P1498)
+ [P1058 立体图](https://www.luogu.com.cn/problem/P1058)