大家好,我是小黄呀。
题目大意
给定一颗二叉树,最大深度为D
,且所有叶子深度都相同(满二叉树)。所有结点按照层次顺序编号为1,2,3,…2^D-1
,并且都有一个开关,初始状态全部关闭。在结点1处放一个小球,每经过一个开关,状态发生改变。当小球到达一结点时,若结点开关关闭,则向左走,否则向右走,直至到达叶子结点。
思路分析
- 初始思路:利用完全二叉树的性质,对于一个结点
k
,其左子结点和右子结点的编号分别是2k
和2k+1
,然后运用数组模拟小球下落,每进过一开关改变状态,并判断走向是左还是右,直到出界。 - 优化思路:
- 由于运用数组模拟盒子移动的程序有一个共同的缺点:运算量太大。
- 而本题中由于每个小球都从根结点出发,因此,相邻两个小球必然是一个在左子树,一个在右子树,所以只需要根据小球编号的奇偶性就可以对小球位置进行判定,从而得出小球运动的方向,然后每层类推。
- 题目中给出小球个数
I
,根据分析,当I
是奇数时,它是往左走的第(I+1)/2
个小球,当I
是偶数时,它是往右走的第I/2
个小球,这样就可以直接模拟最后一个小球的路线,从而大大节省了数组模拟中的缺点。 - 举个例子:例如
D=3,I=5
。在第一层,首先判断第5
个小球是在往根节点的左走还是右走,由于I
为奇数,因此往左走,并且为向左走的第3
个小球。到了第二层,此时对于第二层,可以将小球的编号I
看作3
,此时即为判断第3
个小球对于第二层的根节点是向左走还是向右走,同理,为向左走的第2
个小球。此时小球已到达第三层,为叶子结点。
具体代码
- 初始思路:
#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;
}
- 优化思路:
#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;
}