例题6-6 小球下落(Dropping Balls, UVa 679)
有一棵二叉树,最大深度为D,且所有叶子的深度都相同。所有结点从上到下从左到右
编号为1, 2, 3,…, 2D-1。在结点1处放一个小球,它会往下落。每个内结点上都有一个开关,
初始全部关闭,当每次有小球落到一个开关上时,状态都会改变。当小球到达一个内结点
时,如果该结点上的开关关闭,则往左走,否则往右走,直到走到叶子结点,如图所
示。
![]()
一些小球从结点1处依次开始下落,最后一个小球将会落到哪里呢?输入叶子深度D和
小球个数I,输出第I个小球最后所在的叶子编号。假设I不超过整棵树的叶子个数。D≤20。
输入最多包含1000组数据。
样例输入:
6
4 2
3 4
10 1
2 2
8 128
16 12345
-1
样例输出:
12
7
512
3
255
36358
- 最简单能想到的就是模拟,模拟球的下落,但是输入一旦大了就超时了。
- 所以我们可以,找规律。
首先假设有6层,我们来扔球看看情况
球数 落下位置(最后一行,第一个为0计数)
0 0
1 16
2 8
3 24
4 4
5 20
6 12
7 28
8 2
9 18
10 10
11 26
12 6
13 22
14 14
15 30
---
16 1
17 17
18 9
19 25
20 5
21 21
22 13
23 29
24 3
25 19
26 11
27 27
28 7
29 23
30 15
31 31
==== 第33开始轮回 ===
32 0
33 16
34 8
35 24
36 4
37 20
38 12
39 28
发现规律了吧。
因为按照扔球数量,32为一个大轮回,这缘于底层数32。
再次基础上我们可以按照16个一组进行再分组,然后可见第二组比起第一组,相对扔球数的结果位置数字要大1。比如:17球结果17,比1球结果16,结果大1。23球结果29,比7求结果28,大1。
然后我们再次更小尺度分组,8个一组,可见后一组的结果位置数大前一组2。
总结规律如下:
底层数量 | 分组按照的大小 | 当前尺度的分组的答案偏移量 |
---|---|---|
N | M | P |
32 | 16 | 1 |
8 | 2 | |
4 | 4 | |
2 | 8 | |
1 | 16 |
所以假设要求30球的值,就是
首先减一,对齐从0开始: 30 - 1
被减数 | 组大小 | 组对应的偏移值 |
---|---|---|
29 | 16 | 1 |
13 | 8 | 2 |
5 | 4 | 4 |
1 | 1 | 16 |
0 | 0 |
res = (1+2+4+16) + 2^5(底层数) = 23 + 32 = 55
所以公式如下:
假设层数d,球数n
$$
T(m,0)=0
$$
$$
T(n,d) = (T(\lfloor (n/2) \rfloor,d-1) + (n \mod 2) ) \times 2
$$
或者用循环表示
res = 0
for ( i = 0 to d ):
res = res * 2 + mod(n,2)
n = floor(n / 2)
代码
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
int main()
{
int T;
cin>>T;
while(T--){
int depth,index;
cin>>depth>>index;
int res = 0;
int bottom = pow(2,depth-1); // 底层数量
int waitReverse = (index-1) % bottom;
for(int i=0;i<depth-1;i++){
res <<= 1; // 乘 2
res += (waitReverse & 1); // 模2, 取奇偶
waitReverse >>= 1; // 除2, 向下取整
}
cout<<res + bottom<<endl;
}
int end;
cin>>end;
return 0;
}
// AC at 2020/03/11
ps:拖了4个月了。再拖下去我就没了。