原题地址:http://poj.org/problem?id=1108
==============================
题目大意:
一棵树表示一个窗口,它的叶子节点都是大写字母,非叶子节点是两种字符‘|’和‘-’,表示它的两棵子树分别位于主窗口的上下和左右。给你一棵树,要求画出窗口。
1.每个叶子结点用一个字母表示,对应图中最小的一个窗口,该窗口的左上角写的是这个字母,其他三个角是*号,但如果这个角是其他窗口的左上角,则要显示对应的字母。窗口的上下边用字符‘-’表示,左右边用字符‘|’表示。
2.可以知道,一个大窗口如果被分为上下两个部分,则上下两个小窗口的宽度是相同的,若被分为左右两个窗口,则高度是相同的。而单独一个小窗口应该是2*2的大小,但如果两部分窗口的宽度(或高度)不能相同,则较小的那个窗口要按比例拉伸且左边的(或上边的)窗口计算出来的大小为小数时,要向上取整。
题目输入时是按照先序遍历的方式输入的。
==============================
解题思路:
思路分为两步:
1、先分别计算出每个子窗口最小的大小(高和宽),按后序遍历的方式,如果是叶子节点,则高和宽都是2,若是‘-’窗口,则该窗口的高等于上窗口加下窗口,宽等于两个窗口的高的较大值。若是‘|’同理。按这样的方式计算出来的长宽不是最终的高宽,但最大的那个窗口的窗口的高和宽就是整个图形的高和宽了。
2、根据各个窗口的高和宽,在一个字符数组里逐条画出分隔窗口每条线(先序遍历)。在计算线的位置的时候要按比例计算出每部分窗口的实际宽度和高度。
==============================
解题过程:
1、数据结构:
#define N 2000
struct NODE
{
char c;//保存窗口的字母或者‘-’和‘|’
int k;//窗口的宽度
int h;//窗口的高度
int l,r;//左孩子和右孩子的数组下标
};
NODE node[N];//保存整个二叉树
char ans[N][N];//保存最终的图形
本来想用数组存二叉树的时候,第i个节点的左孩子用第i*2个节点表示,第2*i+1个节点表示右孩子,但做下去RE了,原因是这个二叉树不是完全二叉树,若所有的节点都只有左孩子,则要用到2^36,则必然放不下,因此改成l,r分别表示该节点的左孩子和右孩子的数组下标。
2、二叉树的输入:
由于该二叉树的输入是按先序遍历的方式,而每个叶子节点都是大写字母,因此,在输入的时候用先序遍历的方式,逐个读取字符,若是叶子节点则返回,若不是,则继续读入左孩子和右孩子。
int k;
int chuli()//输入
{
scanf("%c",&node[k].c);
int n=k;
if(node[n].c=='|'||node[n].c=='-')
{
++k;
node[n].l=k;
chuli();
++k;
node[n].r=k;
chuli();
}
else return ;
}
用k作为全局变量,使输入的树的结点按顺序存储在数组中。
3、计算每个窗口的大小:
int f(int n)//计算每个窗口的大小
{
if(node[n].c<='Z'&&node[n].c>='A')
{
node[n].k=node[n].h=;
return ;
}
f(node[n].l);
f(node[n].r);
if(node[n].c=='|')
{
node[n].k=node[node[n].l].k+node[node[n].r].k;
node[n].h=MAX(node[node[n].l].h,node[node[n].r].h);
}
else
{
node[n].k=MAX(node[node[n].l].k,node[node[n].r].k);
node[n].h=node[node[n].l].h+node[node[n].r].h;
}
return ;
}
后序遍历整个二叉树,如果是叶子节点,则高和宽都是2,若是‘-’窗口,则该窗口的高等于上窗口加下窗口,宽等于两个窗口的高的较大值。
4、根据计算好的每个窗口的大小在字符的二维数组ans里画出整个窗口:
初始化为一个高度为node[1].h,宽度为node[1].k的窗口,四个角都用‘*’表示。
//画外框
ans[][]='*';
for(j=;j<node[].k;j++) ans[][j]='-';
ans[][j]='*';
for(i=;i<node[].h;i++)
{
ans[i][]='|';
for(j=;j<node[].k;j++) ans[i][j]=' ';
ans[i][j]='|';
}
ans[i][]='*';
for(j=;j<node[].k;j++) ans[i][j]='-';
ans[i][j]='*';
画每条线和填入字母:
void pr1(int x,int y,int l)//画竖线,以点(x,y)为上端点,画一条长为l的线,其中,两个端点用‘*’表示。
{
int i;
ans[x][y]='*';
for(i=;i<l;i++)
{
ans[x+i][y]='|';
}
ans[x+i][y]='*';
}
void pr2(int x,int y,int l)//画横线
{
int i;
ans[x][y]='*';
for(i=;i<l;i++)
{
ans[x][y+i]='-';
}
ans[x][y+i]='*';
}
int p(int x,int y,int i)//(x,y)表示当前窗口的左上角的坐标,i表示当前节点的数组下标。
{
//p1();
//getchar();
if(node[i].c=='|')
{
node[node[i].l].h=node[node[i].r].h=node[i].h;
node[node[i].r].k=node[i].k*node[node[i].r].k/(node[node[i].r].k+node[node[i].l].k);
node[node[i].l].k=node[i].k-node[node[i].r].k;//按比例分配宽度
//画竖线
pr1(x,y+node[node[i].l].k,node[i].h);
p(x,y,node[i].l);
p(x,y+node[node[i].l].k,node[i].r);
}
else if(node[i].c=='-')
{
node[node[i].l].k=node[node[i].r].k=node[i].k;
node[node[i].r].h=node[i].h*node[node[i].r].h/(node[node[i].r].h+node[node[i].l].h);
node[node[i].l].h=node[i].h-node[node[i].r].h;//按比例分配高度
//画横线
pr2(x+node[node[i].l].h,y,node[i].k);
p(x,y,node[i].l);
p(x+node[node[i].l].h,y,node[i].r);
}
else
{
ans[x][y]=node[i].c;//在窗口左上角填入字母
}
return ;
}
在按比例计算宽度和高度的时候,由于要将第一部分的宽度和高度向上取整,也可以先计算右子树的宽度或高度,再用总宽度或高度减去它计算出左子树的宽度或高度。
5、输出最后的图形:
void p1()//输出窗口
{
int i,j;
for(i=;i<=node[].h;i++)
{
for(j=;j<=node[].k;j++)
{
printf("%c",ans[i][j]);
}
printf("\n");
}
}
6、主函数:
#include<stdio.h>
#include<iostream>
#define N 2000
#define MAX(x,y) (((x)>(y))?(x):(y))
using namespace std;
int main()
{
int t,i,j,cas=;
cin>>t;
while(t--)
{
getchar();
k=;
chuli();//输入
//cout<<"!!"<<endl;
f();//计算窗口大小
//画外框
ans[][]='*';
for(j=;j<node[].k;j++) ans[][j]='-';
ans[][j]='*';
for(i=;i<node[].h;i++)
{
ans[i][]='|';
for(j=;j<node[].k;j++) ans[i][j]=' ';
ans[i][j]='|';
}
ans[i][]='*';
for(j=;j<node[].k;j++) ans[i][j]='-';
ans[i][j]='*';
cout<<cas++<<endl;
p(,,);//处理整个框
p1();//输出
}
}