Loading

小球下落——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);
	}
}
posted @ 2020-11-05 14:54  yudoge  阅读(148)  评论(0编辑  收藏  举报