紫书 例6.6 小球下落(Dropping Balls, UVa 679)

大家好,我是小黄呀。

VJ题目传送门

题目大意

给定一颗二叉树,最大深度为D,且所有叶子深度都相同(满二叉树)。所有结点按照层次顺序编号为1,2,3,…2^D-1,并且都有一个开关,初始状态全部关闭。在结点1处放一个小球,每经过一个开关,状态发生改变。当小球到达一结点时,若结点开关关闭,则向左走,否则向右走,直至到达叶子结点。

思路分析

  1. 初始思路:利用完全二叉树的性质,对于一个结点k,其左子结点和右子结点的编号分别是2k2k+1,然后运用数组模拟小球下落,每进过一开关改变状态,并判断走向是左还是右,直到出界。
  2. 优化思路:
    1. 由于运用数组模拟盒子移动的程序有一个共同的缺点:运算量太大
    2. 而本题中由于每个小球都从根结点出发,因此,相邻两个小球必然是一个在左子树,一个在右子树,所以只需要根据小球编号的奇偶性就可以对小球位置进行判定,从而得出小球运动的方向,然后每层类推。
    3. 题目中给出小球个数I,根据分析,当I是奇数时,它是往左走的第(I+1)/2个小球,当I是偶数时,它是往右走的第I/2个小球,这样就可以直接模拟最后一个小球的路线,从而大大节省了数组模拟中的缺点。
    4. 举个例子:例如D=3,I=5。在第一层,首先判断第5个小球是在往根节点的左走还是右走,由于I为奇数,因此往左走,并且为向左走的第3个小球。到了第二层,此时对于第二层,可以将小球的编号I看作3,此时即为判断第3个小球对于第二层的根节点是向左走还是向右走,同理,为向左走的第2个小球。此时小球已到达第三层,为叶子结点。

具体代码

  1. 初始思路:
#include<cstdio>
#include<cstring>
const int maxd = 20;
int s[1<<maxd];//最大结点数2^maxd-1 
int main()
{
	int D,I;
	while(scanf("%d%d",&D,&I)==2)
	{
		memset(s,0,sizeof(s));//开关 
		int k,n=(1<<D)-1;//n为最大结点编号 
		for(int i=0;i < I; i ++)//让I个小球下落 
		{
			k=1;
			for(;;)
			{
				s[k] = !s[k];
				k=s[k]?k*2:k*2+1;//根据开关状态选择下落方向 
				if(k>n) break;//出界 
			}
		}
		printf("%d\n",k/2);//出界之前的叶子编号 
	 } 
	return 0;
}
  1. 优化思路:
#include<stdio.h>
int main() {
	int n;
	scanf("%d", &n); 
	while(n--) {
		int D, I; scanf("%d %d", &D, &I);
		int k = 1;
		for(int i = 0; i < D-1; i++) 
			if(I%2) 
			{
			 	k *= 2; 
				I = (I+1)/2; 
			}
			else 
			{
			 	k = (k*2+1);
				I /= 2;
			}
		printf("%d\n", k);
	} 
	return 0;
} 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_AoSnow_

创作不易,打赏打赏些8

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值