NYOJ 63 小猴子下落
法一:
1 #include<stdio.h> 2 #include<stdlib.h> 3 int main() 4 { 5 int d,i,k; 6 while(~scanf("%d%d",&d,&i)&&(d||i)) 7 { 8 k=1; 9 while(--d) 10 { 11 if(i&1) 12 {k*=2; i=(i+1)/2;} 13 else 14 {k=2*k+1; i/=2;} 15 } 16 printf("%d\n",k); 17 } 18 system("pause"); 19 return 0; 20 }
法二:
1 #include<stdio.h> 2 int main() 3 { 4 int i,value,depth,base,newValue; 5 while(scanf("%d%d",&depth,&value)&&depth||value) 6 { 7 for(i=base=1;i<depth;i++) 8 base<<=1; 9 for(value--,newValue=i=0;i<depth-1;i++) 10 { 11 newValue=(newValue<<1)+(value&1); 12 value>>=1; 13 } 14 printf("%d\n",newValue+base); 15 } 16 return 0; 17 } 18 本题我是突然想到赫夫曼编码,然后根据其意在题意所描述的满二叉树上编号,结果发现所得的二进制编码,除去最高位只保留深度数减一位,然后化为十进制再加一,就是在这一层上叶子个数的序号,之后再加上这层之前所有的结点个数就是该叶子总的序号,也即是最后一只猴子所到的位置。而且最初得到的二进制编码,恰恰是最后那只猴子的序号减一所对应的二进制数,注意所有的二进制编码位数都是深度减一!循环中用位运算效率较高!
算法分析:刚看这题我就觉得这种题应该会有规律,想了一会还真有。令 a 为第 a 个出去的猴子; 如果 a 为偶数, 说明 a 所在的节点(设ans为 a 所在节点的值) 的开关是开着的,往右走 ans=ans*2+1 , a=a/2 ; 如果 a 为基数 则往左走 ans=ans*2 ,a=a/2+1.
思路:每个小球都会落在根节点上,因为前两个小球必是一个在左字数,一个在右子树。一般的,只需看小球编号的奇偶性,就能指导它是最终在哪棵子树中。对于那些落入根结点左子树的小球来说,只需知道小球是第几个落在根是左子树里面的,就可以知道它下一步是往左还是往右了。依此类推,直至小球落在叶子上。
如果使用题目给出的I,当I是奇数时,它是往左走的第(I+1)/2个小球;当I是偶数时,它是往右走的第I/2个小球。这样可以模拟最后一个小球的路线。
解题思路:
很久前做过这道题,是用的模拟法做的。因为这道题数目的测试数据比较少,所以暴力就过了。
但是如果测试数据很大时,超时是明显的。因为D<=20,所以需要遍历的为2的19次方乘以19,如果有1000组测试数据,就肯定挂了。
今天看到了一种优化,非常巧妙。
因为每个小猴子都是从根节点向下,它必然有两种选择:左、右。而且有规律,它前面的两个猴子一定是左,右。所以在每个节点进入根节点时,我们只需要判断这个点的奇偶性就知道它的方向了。然后它进入根节点的下一层,依然有两种选择,同样的判断,但是数据规模减少一半(每进入1层,舍弃另一个子树,必然减少一半,因为左右的个数相同或者相差1),这样,我们只需要判断这个猴子是第几个进入该层,然后判断奇偶即可。
今天在看二叉树,忽然发现有个题是跟二叉树扯点边,就顺手把它做了。回过头来看这个问题,会发现其实这个题真的很简单。当初之所以没有做而是放到了现在,就是自己犯了想当然的错误,认为这个题是一个要用到二叉树的题,自己没有看二叉树,肯定做不出来。就把它放到一边了····实践证明,这是一道水题·····一次直接水过。以前想的很麻烦,但是后来发现其实这个题很简单。思路也很清晰。不断的模拟小猴子在各个结点的选择。先对输入的小猴子的m编号进行判断,如果它是奇数,那么它就是第(m+1)/2个小猴子,那么它的编号就是2k;偶数的话就是第m/2个小猴子。