题目:
小球下落:
一颗最大深度 D 的二叉树,所有叶子深度相同,
所有节点从上到下,从左到右的编号依次1,2,3,,,,2^D - 1,
在节点1处放一个球往下落。
每个内节点都有一个开关,初始全部关闭,
但每次有小球落到一个开关时状态改变。
若关闭,则左走,否则右走,直到叶子节点。
一共I个球下落,求第I个球最后所在叶子编号;
(I不可超过整棵树的叶子个数,D<=20)
初见可以写出模拟程序:
左走:子节点 = 2 * 父节点 右走: 子节点 = 2 * 父节点 + 1
#include <iostream>
using namespace std;
bool switch[10010];
int main()
{
int D, I;
while(cin>>D>>I){
memset(switch,0,10010*sizeof(bool));
int pos = 1;
int limit = (1 << D) - 1;
for(int i = 0;i< I;++i){
pos = 1;
for(;;){
switch[pos] = !switch[pos];
pos = switch[pos]? 2 * pos : 2 * pos + 1;
//此时已经越界
if(pos>limit) break;
}
}
cout<< (pos>>1) <<'\n';
}
return 0;
}
但运算量量太大,I 可以高达 2 ^ D - 1 ,每组测试数据可能高达 2 ^ 19 * 19 , 而一共可能 1000组数据.
然而我们可以看到 每个小球都会落在根节点上,
因此前两个小球必然一个在 左子树,一个在右子树;
后面,我们可以根据小球编号的奇偶性判断最终叶子编号;
或许下面这个表可以借助理解: 以 0 表 左走, 1 表 右走
以 D = 4_I = 8 为例:
0 0 0
1 0 0
0 1 0
1 1 0
0 0 1
1 0 1
0 1 1
1 1 1
再结合代码想想:
#include <iostream>
using namespace std;
int main()
{
int D , I;
while(cin>>D>>I){
//做 D - 1 次 选择
int pos = 1;
for(int i = 0;i < D - 1;++i){
if(I % 2) { pos = 2 * pos ; I = (I + 1) / 2;}
else { pos = 2 * pos + 1; I /= 2; }
}
cout<< pos <<'\n';
}
return 0;
}
需要说明的是,
if(I % 2) { pos = 2 * pos ; I = (I + 1) / 2;}
对于 i 为奇数 , 则左走 ,容易理解;
再看看下面这幅”第一步图示“:
0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1
0 1 0 1 0 1 0 1 0 1 0 1
观察第 i 个 和 向左走的 第 i 个
思考:为什么想左走 第 i 个球的下一步 与 第 i 个球第一步相同
即将 节点 2 * pos 作为根节点,向左走的第 (i + 1) / 2 个球,相当 在 2 * pos节点下落的 第 (i + 1) / 2 个球 ;
同理, 向右走也是如此;