小球下落——UVa 679
移动盒子
问题描述:
有一棵二叉树,最大深度为D,且所有叶子的深度都相同。所有结点从上到下从左到右编号为1, 2, 3,…, 2D-1。在结点1处放一个小球,它会往下落。每个内结点上都有一个开关,初始全部关闭,当每次有小球落到一个开关上时,状态都会改变。当小球到达一个内结点时,如果该结点上的开关关闭,则往左走,否则往右走,直到走到叶子结点,如图6-2所示。
一些小球从结点1处依次开始下落,最后一个小球将会落到哪里呢?输入叶子深度D和小球个数I,输出第I个小球最后所在的叶子编号。假设I不超过整棵树的叶子个数。D≤20。输入最多包含1000组数据。
样例输入:
4 2
3 4
10 1
2 2
8 128
16 12345
样例输出:
12
7
512
3
255
36358
使用二叉树模拟
#include "iostream"
#include "cstdio"
#include "cmath"
#define MAX_NODE_CNT 1048576
using namespace std;
int D,I;
int state_tree[MAX_NODE_CNT + 1];
int right(int i) {
return i * 2 + 1;
}
int left(int i) {
return i * 2;
}
void clear(int i) {
if (i >= pow(2, D))return;
state_tree[i] = 0;
clear(left(i));
clear(right(i));
}
// 小球下落,返回叶子节点的值
int go(int i) {
int state = state_tree[i];
state_tree[i] = !state_tree[i];
if (left(i) >= pow(2, D))return i;
if (state)
return go(right(i));
else
return go(left(i));
}
int main() {
int i,r;
while (scanf("%d %d", &D,&I) != EOF) {
clear(1);
for (i = 0; i < I; i++) {
r = go(1);
}
printf("%d\n", r);
}
return 0;
}
如上的代码的确能得出正确的结果,但是,每次寻找一个叶子节点需要D-1
个节点。然后,叶子节点的个数为2^D-1
个,也就是I最大有2^D-1
个。输入最多有1000个,所以程序可能要算个1000*(D-1)*(2^D-1)
次才能出结果,淦!!!
模拟最后一个小球的路线
考虑对于根节点,第一个小球会向左子树那边滚,第二个小球会向右子树那边滚。
把条件放的更宽一点,对于根节点,如果是第奇数个小球,那么这个小球最后必定落到左子树上,如果是第偶数个小球,那么它必定落在右子树上。
当第一个小球从根节点落到节点2时,我们又可以把2看作根节点。
对于第n个经过节点2的小球,如果I是奇数,那么它必定出现在节点2的左子树,否则必定出现在节点2的右子树。
递归下去,对每个节点都是这样,关键我们要找到这个n,即第几个通过节点x的小球。
对于根节点,这个数就是I。然后对于根的左子节点,第I个小球是第I+1/2
个经过这个节点的小球,而对于右子节点,则是第I/2
个经过这个节点的小球。
代码
int main() {
int D, I;
while (scanf("%d%d", &D, &I) == 2) {
int k = 1;
for (int i = 1; i < D; i++)
if (I % 2 == 1) { // 向左走
k = k * 2; I = (I + 1) / 2;
}
else { // 向右走
k = k * 2 + 1; I = I / 2;
}
printf("%d\n", k);
}
}