6_6小球下落uva679(刷题笔记)
题目
思考
- 观察树可得,对于任意一个非叶子节点k,其左节点、右节点分别为2k和2k+1。
- 每个一个节点都有一个标记false或true,很容易想到使用一个数组tree[]存储每个节点的标记。
暴力模拟
#include<cstdio>
#include<cstring>
using namespace std;
bool tree[600000];//标记数组
int main(){
int T,D,I,p;//T数据组数;D树深度,I掉落小球编号,p为节点编号
scanf("%d",&T);
while(T--){
scanf("%d %d",&D,&I);
//模拟编号为1~i小球掉落
for(int i = 1;i<=I;i++){
p = 1;//每个小球每次从头开始掉落
//暴力模拟每个小球掉落过程,掉落层数为d-1次
for(int k = 1;k<D;k++){
if(!tree[p])tree[p] = true,p = p*2;
else tree[p] = false,p=p*2+1;
}
}
printf("%d\n",p);//输出叶子节点
memset(tree,false,sizeof(tree));//重置
}
scanf("%d",&T);//输入-1
return 0;
}
算法优化
以上算法每一次模拟小球掉落后,都要对tree[]数组进行重置,这对于大数据的测试数据来说数据量太大。
观察运行结果,其实每一步小球的行走路径是可以通过小球编号的奇偶性判断,如2号球经过第一个节点时,由于1号球改变了该节点的状态为true,所以2号球必定往右孩子方向走。
由此可推出:
- 任何一个奇数编号球I=2k+1,在经过某个节点的时候,它一定是第(I+1)/2个经过该节点的编号球;
- 任何一个偶数编号球I=2k,在经过某个节点的时候,它一定是第I/2个经过该节点的编号球。
所以只要知道一个球是第几个经过该节点的球,就可以依次递推。
#include<cstdio>
#include<cstring>
using namespace std;
bool tree[600000];//标记节点
int main(){
int T,D,I,p;//T数据组数;D树深度,I掉落小球编号
scanf("%d",&T);
while(T--){
scanf("%d %d",&D,&I);
int k = 1;
//直接模拟该小球的掉落路径即可,不需要将前面的所有小球都模拟一遍
for(int i = 1;i<D;i++){
if(I%2){//小球编号为奇数时,走左孩子
k = k*2;
I = (I+1)/2;//它是第(I+1)/2个经过左孩子的球
}else{//小球编号为偶数时,走右孩子
k = k*2+1;
I = I/2;//它是第I/2个经过该右孩子的球
}
}
printf("%d\n",k);
}
return 0;
}