【Luogu】P1185 绘制二叉树

挺有意思的一道大模拟,我用的方法似乎也比较特殊。那,发篇题解吧(虽然审核通道关了


事实上不太难。

我们知道,深度为 x x x 的满二叉树,其节点数为 2 x − 1 2^x-1 2x1。因此,我们可以作出这样的预处理:

for(int i=1;i<(1<<m);i++)
{
	a[i]=1;//a_i 表示编号为 i 的节点是否删除
}

同时,我们发现,如果一个节点删除了,那么其儿子也都会删除。所以,我们可以用一个递归来删除节点:

void del(int x)
{
	a[x<<1]=0;
	a[x<<1|1]=0;//删除
	if(x<=(1<<m-1))//边界条件
	{//递归删除
		del(x<<1);
		del(x<<1|1);
	}
}

可以看出,如果我们从根节点开始绘图,会很麻烦。因此,我们可以从叶子节点开始画。

那么整个输入的过程就搞定了:

cin>>m>>n;
for(int i=1;i<(1<<m);i++)
{
	a[i]=1;
}
while(n--)
{
	int i,j;
	cin>>i>>j;
	int k=(1<<i-1)+j-1;
	a[k]=0;
	a[k<<1]=0;
	a[k<<1|1]=0;
	del(k<<1);
	del(k<<1|1);
}

接下来就是最重要的一环了。

当然肯定要递归绘画,因此用函数 sol() 来绘制(我们先画原始的满二叉树)。

我们用 a n s i , j ans_{i,j} ansi,j 来存储结果。

首先,是绘制前的空格。可以发现,空格个数从下到上是递增的。因此我们可以用一个变量 d e p dep dep 来表示绘制的层数(也就是行数),不仅可以绘制空格,最后输出时也很有用。

同时,我们用 h h h 来表示当前绘制列数。

那就可以这样写:

int h=0;
for(int i=1;i<dep;i++)
{
	ans[dep][h+i]=' ';
}
h+=dep;

接下来我们来说说 sol() 的参数。

我们用整数 x x x 表示当前绘制的节点数(也就是 o 的个数),用 s p _ l r sp\_lr sp_lr 表示同一个子树中左儿子和右儿子间的空格数(初始为 3 3 3),用 s p _ r l sp\_rl sp_rl 表示左子树的右儿子、右子树的左儿子间的空格数(初始为 1 1 1)。注意到后面时,会保证 s p _ l r = s p _ r l sp\_lr=sp\_rl sp_lr=sp_rl

然后我们分两步:画节点和画连接线。

先是画节点(比较简单,不多说):

for(int i=1;i<=x;i++)
{
	ans[dep][h]='o';//左儿子
	for(int j=1;j<=sp_lr;j++)
	{
		ans[dep][h+j]=' ';
	}//左儿子与右儿子间的空格数
	ans[dep][h+sp_lr+1]='o';//右儿子
	for(int j=1;j<=sp_rl;j++)
	{
		ans[dep][h+sp_lr+1+j]=' ';
	}//左子树右儿子与右子树左儿子间的空格数
	h+=sp_lr+sp_rl+2;
}

然后是画连接线。

画连接线时要注意, d e p dep dep 要加一, h h h 要归零。

而且,连接线间的空格一开始为 s p _ l r − 1 sp\_lr-1 sp_lr1,随后逐渐 − 2 -2 2,直到 = 1 =1 =1

因此代码如下:

for(int i=sp_lr-2;i>=1;i-=2)//间距逐渐缩小
{
	for(int j=1;j<=x;j++)//x 对连接线
	{
		for(int k=1;k<dep;k++)
		{
			ans[dep][h+k]=' ';
		}
		h+=dep;
		ans[dep][h]='/';
		for(int k=1;k<=i;k++)
		{
			ans[dep][h+k]=' ';
		}
		h+=i+1;
		ans[dep][h]='\\';
		for(int k=1;k<=dep;k++)
		{
			ans[dep][h+k]=' ';
		}
		h+=dep;
	}
	dep++;
	h=0;//每次操作完 dep 都要加一,h 都要归零
}

每次做完这两样操作,就可以继续递归了:

sol(x>>1,sp_lr==3?5:(sp_lr+1<<1)-1,sp_lr==3?5:(sp_lr+1<<1)-1);
//x 每次除以二;sp_lr 随后与 sp_rl 相等

而边界,则是 x = 0 x=0 x=0 的时候:

if(x==0)
{
   ans[dep][h]='o';//绘制根节点
	return;
}

而一开始 x x x 的值,便是 2 m − 1 2 = 2 m − 2 \frac{2^{m-1}}{2}=2^{m-2} 22m1=2m2


接下来又是重中之重——删除节点。

首先绘制完的完整的满二叉树,有 d e p dep dep 行,有 2 m − 2 × 5 + 2 m − 2 − 1 2^{m-2} \times 5+2^{m-2}-1 2m2×5+2m21 列(可以自己算一下)。然后我们倒序遍历,用 s s s 表示当前搜索到了几个节点,如果 a s = 0 a_s=0 as=0,那么这个节点就是要删除的。

删除时要注意,还要删掉自己与儿子间、与父亲间的连接线。

就像这样:

for(int i=dep;i>=1;i--)
{
	for(int j=1;j<=(1<<m-1>>1)*5+(1<<m-1>>1)-1;j++)
	{
		if(ans[i][j]=='o')//是节点
		{
			if(!a[++s])//要删除
			{
				ans[i][j]=' ';
				for(int k=1;ans[i-k][j-k]!='o'&&ans[i-k][j-k]!=' '&&i-k>=1&&j-k>=1;k++)
				{
					ans[i-k][j-k]=' ';
				}
				for(int k=1;ans[i-k][j+k]!='o'&&ans[i-k][j+k]!=' '&&i-k>=1&&j+k<=(1<<m-1>>1)*5+(1<<m-1>>1)-1;k++)
				{
					ans[i-k][j+k]=' ';
				}
				for(int k=1;ans[i+k][j+k]!='o'&&ans[i+k][j+k]!=' '&&i+k<=dep&&j+k<=(1<<m-1>>1)*5+(1<<m-1>>1)-1;k++)
				{
					ans[i+k][j+k]=' ';
				}
				for(int k=1;ans[i+k][j-k]!='o'&&ans[i+k][j-k]!=' '&&i+k<=dep&&j-k>=1;k++)
				{
					ans[i+k][j-k]=' ';
				}//四个方向都有可能
			}
		}
	}
}

最后,再输出即可。


完整 AC Code:

#include
using namespace std;

int m,n,dep=1,a[3005];
char ans[3005][3005]; 

void del(int x)
{
	a[x<<1]=0;
	a[x<<1|1]=0;
	if(x<=(1<<m-1))
	{
		del(x<<1);
		del(x<<1|1);
	}
}

void sol(int x,int sp_lr=3,int sp_rl=1)
{
	int h=0;
	for(int i=1;i<dep;i++)
	{
		ans[dep][h+i]=' ';
	}
	h+=dep;
	if(x==0)
	{
		ans[dep][h]='o';
		return;
	}
	for(int i=1;i<=x;i++)
	{
		ans[dep][h]='o';
		for(int j=1;j<=sp_lr;j++)
		{
			ans[dep][h+j]=' ';
		}
		ans[dep][h+sp_lr+1]='o';
		for(int j=1;j<=sp_rl;j++)
		{
			ans[dep][h+sp_lr+1+j]=' ';
		}
		h+=sp_lr+sp_rl+2;
	}
	dep++;
	h=0;
	for(int i=sp_lr-2;i>=1;i-=2)
	{
		for(int j=1;j<=x;j++)
		{
			for(int k=1;k<dep;k++)
			{
				ans[dep][h+k]=' ';
			}
			h+=dep;
			ans[dep][h]='/';
			for(int k=1;k<=i;k++)
			{
				ans[dep][h+k]=' ';
			}
			h+=i+1;
			ans[dep][h]='\\';
			for(int k=1;k<=dep;k++)
			{
				ans[dep][h+k]=' ';
			}
			h+=dep;
		}
		dep++;
		h=0;
	}
	sol(x>>1,sp_lr==3?5:(sp_lr+1<<1)-1,sp_lr==3?5:(sp_lr+1<<1)-1);
}

int main()
{
	cin>>m>>n;
	for(int i=1;i<(1<<m);i++)
	{
		a[i]=1;
	}
	while(n--)
	{
		int i,j;
		cin>>i>>j;
		int k=(1<<i-1)+j-1;
		a[k]=0;
		a[k<<1]=0;
		a[k<<1|1]=0;
		del(k<<1);
		del(k<<1|1);
	}
	sol(1<<m-1>>1);
	int s=0;
	for(int i=dep;i>=1;i--)
	{
		for(int j=1;j<=(1<<m-1>>1)*5+(1<<m-1>>1)-1;j++)
		{
			if(ans[i][j]=='o')
			{
				if(!a[++s])
				{
					ans[i][j]=' ';
					for(int k=1;ans[i-k][j-k]!='o'&&ans[i-k][j-k]!=' '&&i-k>=1&&j-k>=1;k++)
					{
						ans[i-k][j-k]=' ';
					}
					for(int k=1;ans[i-k][j+k]!='o'&&ans[i-k][j+k]!=' '&&i-k>=1&&j+k<=(1<<m-1>>1)*5+(1<<m-1>>1)-1;k++)
					{
						ans[i-k][j+k]=' ';
					}
					for(int k=1;ans[i+k][j+k]!='o'&&ans[i+k][j+k]!=' '&&i+k<=dep&&j+k<=(1<<m-1>>1)*5+(1<<m-1>>1)-1;k++)
					{
						ans[i+k][j+k]=' ';
					}
					for(int k=1;ans[i+k][j-k]!='o'&&ans[i+k][j-k]!=' '&&i+k<=dep&&j-k>=1;k++)
					{
						ans[i+k][j-k]=' ';
					}
				}
			}
		}
	}
	for(int i=dep;i>=1;i--)
	{
		for(int j=1;j<=(1<<m-1>>1)*5+(1<<m-1>>1)-1;j++)
		{
			cout<<ans[i][j];
		}
		cout<<endl;
	}
 	return 0;
}

你可能感兴趣的:(Luogu,模拟,c++,字符串)