理解小球下落(Dropping Balls)

题目

来源

《算法竞赛入门经典》6.3节树和二叉树例题6-6(P236)

内容

有一棵二叉树,最大深度为D,且所有叶子的深度都相同。所有结点从上到下从左到右
编号为1, 2, 3,…, 2D-1。在结点1处放一个小球,它会往下落。每个内结点上都有一个开关,
初始全部关闭,当每次有小球落到一个开关上时,状态都会改变。当小球到达一个内结点
时,如果该结点上的开关关闭,则往左走,否则往右走,直到走到叶子结点,如图6-2所
示。

在这里插入图片描述
树是一颗满二叉树。

直接模拟最后一个球的过程

在这里插入图片描述
为啥是这样的啊?

有I个球往下落。
比如I = 9,那么就是有9个球往下落,假设他们的编号为1~9,刚开始开关都闭合,只看第一个根结点处的开关

开始开关闭合,那球1会往左子树落;然后开关打开,球2往右子树落;开关闭合,球3往左子树落…

所以奇数号的球往左子树落,偶数号的球往右子树落。那只看往左子树落的球:

左落的球编号13579
(I+1) / 212345

所以有当I是奇数时,它是往左走的第 ( I + 1 ) / 2 (I+1)/2 (I+1)/2个小球;

再看往右落的球:

右落的球编号2468
I / 21234

所以有当I是偶数时,它是往右走的第 I / 2 I/2 I/2个小球;


代码描述

在这里插入图片描述


举个栗子:
输入4 2(高度为2,有两个球要下落)
在这里插入图片描述
直接模拟最后一个球(编号为2,蓝色表示)的下落过程,树总共有D层,很明显,小球只需要下落D-1层就一定到达叶子节点。

设置k表示该球处于树上的结点编号,初始为1(在根结点处)
I表示的是对于所处的树结点来说,第几个经过该结点的球。(初始为I,因为相对于根结点来说,最后一个下落的球是第I个经过根结点的球)
在这里插入图片描述

  • I = 2 I = 2 I=2为偶数,说明它是根结点处1 I / 2 = 1 I/2 = 1 I/2=1)个往子树下落的球,让它下落一层去往右子结点。所处结点更新 k = 2 ∗ k + 1 = 3 , I / 2 = 1 k = 2*k+1 = 3,I/2 = 1 k=2k+1=3I/2=1
    在这里插入图片描述

  • I = 1 I = 1 I=1为奇数,说明它是树结点3这里1 ( I + 1 ) / 2 = 1 (I+1)/2 = 1 (I+1)/2=1)个往子树下落的球,让它下落一层去往左子结点。所处结点更新 k = 2 ∗ k = 6 , ( I + 1 ) / 2 = 1 k = 2*k = 6,(I+1)/2 = 1 k=2k=6(I+1)/2=1
    在这里插入图片描述

  • I = 1 I = 1 I=1为奇数,说明它是树结点6这里1 ( I + 1 ) / 2 = 1 (I+1)/2 = 1 (I+1)/2=1)个往子树下落的球,让它下落一层去往左子结点。所处结点更新 k = 2 ∗ k = 12 , ( I + 1 ) / 2 = 1 k = 2*k = 12,(I+1)/2 = 1 k=2k=12(I+1)/2=1
    在这里插入图片描述
    得知,该球下落了 D − 1 = 4 − 1 = 3 D-1 = 4-1 = 3 D1=41=3层后所处叶结点的编号为12。

完整程序

#include<iostream>
#include<cstring> 
using namespace std;
const int maxd = 20;
bool s[1<<maxd];
int main(){
	//树的高度和将要下落的小球的个数 
	int d, I;
	while(scanf("%d%d", &d, &I) == 2){
		/*1. 模拟所有小球的走向 
		//n为该树结点的最大编号 
		int k, n = (1<<d)-1;
		//初始情况所有开关都为闭合状态 
		memset(s, 0, sizeof(s));
		for(int i=0; i<I; i++){
			//每个小球都从1号结点开始下落 
			k = 1; 
			while(true){
				s[k] = !s[k];//更新开关状态,必须先更新,不然下面k值变化了 
				//因为开关先转状态了,所以规则反了:开启左落,闭合右落
				k = s[k] ? 2*k : 2*k+1;
				//落出界了 
				if(k > n) break;
			}
		} 
		*/
		//2. 直接模拟最后一个小球的走向
		int k = 1;
		for(int i=0; i<d-1; i++){
			//每个小球都是严格下落d-1层
			if(I%2){
				//I是奇数,说明它是向左下落的第(I+1)/2个小球
				k = 2*k;//往左落一层
				I = (I+1)/2; 
			}
			else{
				//I是偶数,说明它是向右下落的第I/2个小球 
				k = 2*k+1;//往右落一层 
				I = I/2; 
			}
		} 
		//1. printf("%d\n", k/2);
		printf("%d\n", k); 
	} 
	return 0;
}
  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值