小球下落 Dropping Balls
题面翻译
更详尽翻译请参考紫书。
许多的小球一个一个的从一棵满二叉树上掉下来组成一个新满二叉树,每一时间,一个正在下降的球第一个访问的是非叶子节点。然后继续下降时,或者走右子树,或者走左子树,直到访问到叶子节点。
决定球运动方向的是每个节点的布尔值。最初,所有的节点都是 FALSE,当访问到一个节点时,如果这个节点是 FALSE,则这个球把它变成 TRUE,然后从左子树走,继续它的旅程。如果节点是TRUE,则球也会改变它为 FALSE,而接下来从右子树走。满二叉树的标记方法如下图。
因为所有的节点最初为 FALSE,所以第一个球将会访问节点 1,节点 2 和节点 4,转变节点的布尔值后在在节点 8 停止。第二个球将会访问节点 1、3、6,在节点 12 停止。明显地,第三个球在它停止之前,会访问节点 1、2、5,在节点 10 停止。
现在你的任务是,给定新满二叉树的深度 d 和下落的小球的编号 i ,可以假定I不超过给定的新满二叉树的叶子数,写一个程序求小球停止时的叶子序号p。
题目描述
输入格式
输出格式
样例
样例输入 #1
5
4 2
3 4
10 1
2 2
8 128
样例输出 #1
12
7
512
3
255
思路分析
一开始可以以一种稍显“粗暴”的方式去分析问题,小球会一层层掉落,已知高度有限且易知一个简单的事实,单看一条走到叶结点的路径,其经过的所有非叶结点都对应一个深度,把最深的叶结点提前算好,那么第i个小球从根结点开始掉落经过的所有非叶结点在对应深度上的编号就是i,并用数组记录每个结点经过对应深度的树的编号即可,判断向左还是向右用一个标记数组记录即可。(紫书上有更精妙的解法,以下作为一种思路的开拓)
代码实现
#include <iostream>
#include <cstring>
using namespace std;
const int I = 524288;
const int MAXDEEP = 20;
int num[I + 1][MAXDEEP + 1]; // 在给定的n个小球和深度depth下,用于存放最后一个小球落到的位置
int tree[1024 * 1024]; // 一棵最深的满二叉树对应的结点树量是:2^20
int main(){
memset(tree, 0, sizeof(tree));
for(int i = 1; i <= I; i++){
int idx = 1;
for(int j = 1; j <= MAXDEEP; j++){
num[i][j] = idx; // 记录小球的编号i
if(tree[idx] == 0){ // 0向左走
tree[idx] = !tree[idx]; // 改变开关状态
idx = 2 * idx;
}else{ // 1向右走
tree[idx] = !tree[idx];
idx = 2 * idx + 1;
}
}
}
int caseNumber;
int depth, n;
cin >> caseNumber;
while(caseNumber--){ // 利用已计算好的结果直接输出
cin >> depth >> n;
cout << num[n][depth] << endl;
}
return 0;
}