关于进制的一些思考

来源:互联网 发布:linux vi怎么用 编辑:程序博客网 时间:2024/06/10 02:09

让我们循序渐进,一个问题一个问题的分析。有类似的进制问题欢迎交流,我来汇总。

因为编辑器不支持下标,所以用log(x,y)的含义是x ^ log(x,y) = y

 

问题1一个地主雇一个长工干七天活,每天给一两黄金,他手里有一个整块的七两黄金,分成三块,恰好可以付工钱,问如何分割?

 

分成1,2,4

第一天给1

第二天把1要回来,给2

第三天给1

第四天把1,2要回来,给4

第五天给1

第六天把1要回来,给2

第七天给1,结束。

 

分析这道题可以发现这是一个2进制的题目,要确保长工手上的值可以保持1~N,最好的办法就是按照2进制序列将N分割。

所以假如不是7,而是100,那么就应该分成1,2,4,8,16,32,37。可以看到最后一个值不是2的幂,这是因为总量100不是2^k - 1,对于这种情况,实际上有多种分法,只要保证如下条件:序列按非递减排序,相邻两个值的倍数不大于2。例如对于100,切分成1,2,4,7,14,28,44也是可以的。

 

这里有两个结论:

1> 按照2的幂序列:1,2,4,……切分,最后不够2的幂就让它单独为1块,那么这个切法是块数最少的切法,即不可能有更少的切法可以表示1~N

2> 如果N == 2^k - 1,那么切法唯一,否则切法不唯一。


问题2给定长度的尺子,尺子没有刻度,即这种尺子只能量它本身的长度,如果需要丈量1~40的长度,至少需要多少把这样的尺子?

 

也许有人立马会说,和上题一样,2进制,那就至少需要1,2,4,8,16,32,共6把尺子。

 

实际的答案是1,3,9,274把尺子。例如想丈量5,那么就把9放在下面,在它的一侧放上13,那么剩下的长度就是5。其它长度相信聪明的读者也能自行完成。

 

这个题和上个题的区别在于,尺子长度可以被减去,也就是说尺子的状态可以是-1(被减去),0(不参加),1(被加上);而上题的分金子问题,金子只有两种状态,0(不给长工),1(给长工),没有其他状态。

 

所以这道题是一道3进制的问题,类似上题有两个结论:

1>按照3的幂序列:1,3,9,……切分,最后不够3的幂就让它单独为1块,那么这个切法是块数最少的切法,即不可能有更少的切法可以表示1~N

2>如果N ==3^k - 1/ 2,那么切法唯一,否则切法不唯一。


问题3找出2.5亿个32bit整数中只出现一次的整数总个数,内存600M900M(我在一个已有题目上加了后面一问,并限制整数为32bit,如果是64bit就不能这么考虑了,原题地址http://topic.csdn.net/u/20070929/14/b183cd03-d780-4c59-a666-ab127f12f7b1.html

 

首先可以肯定的是这是一个位图题,其它任何方法效率都不可能高。主要是看位图如何设计。

 

首先我们看一下600M的情况,32bit的整数,共有2^32=4G种可能,如果用1bit表示整数是否出现,那么需要1bit*4G = 512M bytes内存,可是1bit只能区分整数是否出现,如果出现多次,那就没有区分能力了。所以有人提出了用2bit表示状态:00没出现,01出现1次,10出现多次。这时内存就无法装下所有整数的位图,但是可以装下一半。数据读两次,第一次只考虑负整数,第二次只考虑正整数,把两次结果累加就可以得到结果了。这样做的缺点就是需要读两次数据。这是目前我觉得最好的办法了。

 

但是如果内存增加到900M呢?因为如果每个整数用2bit表示状态,共需要2bit*4G = 1024M bytes内存,还是无法一次装下全部,那么是否还是按照上面600M的解法,读两次数据呢?仔细观察,我们发现,其实2bit用在3种状态下是浪费的,因为11这种情况没有用到,进而我们就应该想如何用更加节省的办法。既然每个整数有3种状态(出现0次,1次,多次),那么其实就是3进制,而计算机的内存资源是按照2进制排列的。我们需要想一下如何用x bits2进制数据表示y bits3进制数据。

2^x >= 3^y

x >= log(2,3) * y1.5849625007211561814537389439478 y

也就是说,这个方法的极限也就是用1.58496250072115618145373894394782进制bits表示13进制bit。恰好可以找到2^8 = 256 > 243 = 3^5 8 / 5 = 1.6已经很接近这个极限了。

所以题目所需的是512M3进制可以用 512M * 1.6 = 819.2M2进制内存容纳。

所以在900M内存的情况下只需要读一次数据就可以统计出结果,当然这个2进制和3进制转换的部分会比较麻烦。


问题4 1000瓶药只有1瓶有毒,药水可以混合,小白鼠喝到有毒的1小时就会死亡,给你1小时时间,问至少多少只小白鼠可以试出是哪瓶有毒?

 

先插播两个错误答案:)

经常搞矩阵的人想,嗯,搞成矩阵,通过行和列唯一确定一个元素,32 * 32 > 1000,那就搞个32*32的矩阵,32个老鼠按行喝,32个老鼠按列喝,那就64只老鼠搞定。

玩魔方的人一般对3维立体的东西比较感兴趣,一看到这题,嘿!不就是10阶魔方吗?将1000瓶药水放到10*10*10的魔方,然后每个老鼠喝一个平面药瓶的药水,在三维空间,3个正交平面可以交与1点,这就是毒药。所以这种方法派10 + 10 + 10 = 30 只老鼠搞定!

 

实际上,这是一道2进制的问题,答案是10只老鼠。老鼠的生和死分别是两种状态,对应2进制的(01),那么102进制位可以表达2^10 = 1024种状态,而1000瓶药水里面只有1瓶有毒,总共只有C(1000,1) = 1000种可能,状态空间足够用。

将老鼠看成102进制位,bit0 ~ bit9,将1000瓶药顺序编号从0~999,将其转换成2进制,如果相应bit1,就给那个bit对应的老鼠喂一滴,例如325号瓶的2进制是101000101,由于bit0bit2bit6bit81,那么就给这4只老鼠喂,其它不喂。这样最后观察那些老鼠死了,就可以确定有毒药的瓶子编号哪些二进制bit1,就找出了这个瓶子。

 

所以验证N瓶药水,就需要ceil(log(2,N))只老鼠。


问题5 700瓶药水有一瓶有毒,药水可混合,喂食小白鼠,20小时会显效果,如果给你50小时,问至少需要多少只小白鼠可以确定哪瓶是毒药?

 

有客官发现50个小时可以做两次试验,估计了一下,用上题的方法,700只做一次试验需要10只老鼠。如果分两次,每次可以验证350瓶药水,每次需要9只,假如第一次就试验出有毒的药瓶,那么就不需要做第二次试验了;反之假如第一次没试出有毒的,那么9只老鼠依然存活,那就可以用它们做第二次试验。所以比只做一次试验要少1只。

 

然而还有用更少只老鼠的方法。因为700瓶药水太多,那就先从简单的考虑,假设9瓶药水,其它条件不变,将9瓶药水按照3进制编号

00,01,02,10,11,12,20,21,22

两只老鼠ab分别对应bit0bit1(注意每种bit3种取值)

1> 第一次试验,a老鼠喝所有bit0 == 0的药水(00,10,20),b老鼠喝bit1 == 0的药水(00,01,02

如果都死了,那么可以判断是00goto 4

如果ab活,那么可疑的药水是(10,20),goto 2

如果ab死,那么可疑的药水是(01,02),goto 2

如果都活,那么可疑的药水是(11,12,21,22),goto 3

2> 1只活老鼠,2瓶药水,随便喂其中1瓶,如果老鼠死了,那么就是这瓶有毒,否则就是另一瓶,goto 4

3> 2只活老鼠,4瓶药水,一次实验机会,可以用上一题2进制的解法得到答案,goto 4

4> 结束

假如你自己画个图,就会发现规律,3只老鼠可以验证3^3 =27瓶药水,N瓶药水需要ceil(log(3,N))个老鼠来验证,对于本题来说,700瓶药水需要6只老鼠。

 

等等,假如时间延长了,不是50小时,例如60小时,那答案是多少呢?由于60小时可以试验3次,所以这个问题就已经不是3进制了。因为每个老鼠的状态可以是(第一次试验死,第二次试验死,第三次试验死,不死),所以这是一个4进制的问题。有兴趣的同学可以用16瓶药水验证一下,理论上60小时16瓶药水需要2只小白鼠。

 

那么通用的问题是N瓶药水里面有一瓶是有毒的,药水可以混合,每次验证需要X小时,总时间Y小时,问至少需要多少只小白鼠?

通用的答案是( Y >= X ) ? ceil(log(floor(Y/X)+1,N)): noSolution

此题答案来自于 zhangchaoxu@sogou,出题者未知。



原创粉丝点击