达拉崩吧的酒宴 | 感受算法的魅力

1. 达拉崩吧的酒宴

成绩10开启时间2019年07月29日 星期一 10:00
折扣0.8折扣时间2019年08月6日 星期二 00:00
允许迟交关闭时间2019年10月8日 星期二 00:00

Description

很久很久以前,巨龙突然出现,带来灾难带走了公主又消失不见,王国十分危险,世间谁最勇敢,一位勇者赶来大声喊:“我要带上最好的剑,翻过最高的山,闯进最深的森林,把公主带回到面前”……最后,英雄 达拉崩巴斑得贝迪卜多比鲁翁 ,他战胜了巨龙 昆图库塔卡提考特苏瓦西拉松 ,国王把公主 米娅莫拉苏娜丹妮谢莉红  嫁给了 达拉崩巴斑得贝迪卜多比鲁翁 

于是国王第二天要在 蒙达鲁克硫斯伯古比奇巴勒城  举办酒宴,一共准备了 N 桶酒。此时一位忠诚的仆人来报,有人在其中一桶酒里下毒,并且此毒要经过24小时后才会毒发身亡。现在距离晚会酒宴开始还有正好24小时,为在酒宴开始前找出哪一桶酒有毒,国王决定找一些小白鼠来试酒。(仆人不可能说谎,即一定有且只有一桶酒有毒

聪明的 达拉崩巴斑得贝迪卜多比鲁翁  本着善良的天性,决定用尽量少的小白鼠来试酒。

请问最少需要多少只小白鼠试酒?

Input

样例输入有多组。

第一行输入整数 T,表示有 T 组用例,

接下来 T 行, 对于魅族测试用例输入一个正整数 n 表示一共有 n 桶酒。

Output

对于每个用例输出一行一个数字,代表所需的最少小白鼠数量。

Sourse

北京理工大学2018年暑假集训算法总结赛 - Problem D

 测试输入期待的输出时间限制内存限制额外进程
测试用例 1 
  1. 3↵
  2. 2↵
  3. 3↵
  4. 4↵
 
  1. 1↵
  2. 2↵
  3. 2↵
1秒64M0

一、编码法来理解

本题本质是一个编码计数的问题,但是你不知道也不太要紧(我第一次做也很懵)...

这么分析吧:每一桶酒都有两种状态 —— 被喝/不被喝,每一只喝了酒的小白鼠都有两种状态 —— 死亡/存活。那么就会想到用二进制编码来对每一桶酒进行区分。下面以1000桶酒为例分析~

1、对酒编号

先按照二进制数字大小,对每一桶酒贴上标签:

0000000000(第1桶)
0000000001 (第2桶)
0000000010 (第3桶)
......
1111100111 (第1000桶)
......
1111111111  (第1024桶)

不难计算,因为2^9 <1000<2^{10},所以1000桶酒需要10位二进制数来表示。

2、小鼠试毒

为每一只小白鼠用数字编号,1、2、3、4...主要是为了区分。然后让小鼠试毒:

从编号倒数第1位是1的所有的桶里面取出1滴混在一起,给编号为1的小白鼠喝。
从编号倒数第2位是1的所有的桶里面取出1滴混在一起,给编号为2的小白鼠喝。
从编号倒数第3位是1的所有的桶里面取出1滴混在一起,给编号为3的小白鼠喝。

以此类推...比如说:第一桶酒不会给到小白鼠喝,第二桶酒只会给到第1只小白鼠喝,第三桶酒只会给到第2只小白鼠喝......第1000桶酒会给到第1、2、3、6、7、8、9、10只小鼠喝。

1000桶酒的编码是10位,所以我们需要10只小白鼠来试毒。那么真的10只小鼠就可以区分出那一桶毒酒吗?怎么通过最后的小鼠死亡结果分析,你接着往下看。

3、结果分析

24小时过去了,就可以来验尸了。记下有哪些编号的小白鼠悲惨地死掉了,就能推出毒酒。

如果没有小白鼠死亡。即00000 00000,那么肯定是第1桶酒有毒。
如果只有第1个小白鼠死了。即00000 00001,则第2桶有毒。
如果只有第1、4个小白鼠死了。即00000 01001,则第10桶有毒。

或者这样更好理解:如果编号为1000100011的酒是毒酒,则对应喝酒的只有第一只,第二只,第六只,第十只死亡。

4、加深理解

由于每一桶酒的编码都是独一无二的,所以每一桶酒给小鼠喝的分配情况都是独一无二的(不会出现某两桶酒都是给同样几只小鼠喝的情况),所以某桶如果是有毒的话对应最后小鼠死亡的结果是独一无二的,故我们一定可以这样来确认出那一桶独一无二的毒酒。

如果上面没看太懂,你就先理解这个,再反复看:10只小鼠有2^{10}种最后死亡的结果,所以最多可以对应1024桶酒。

5、总结

N桶酒,老鼠所能表示的状态数目为M,则需要的老鼠个数为 K=log_M{N}

而具体实验的操作方法为:

  1. 每种状态按照 M 进制进行编码,编码长度为 K
  2. 每个老鼠分别去拿自身的 M 中状态去实验 N 的 M 进制编码的某一位
  3. 所以 K 个老鼠,等同于是 K 长度 M 进制的对应的每一位
  4. 这样试验完后,就确定了每一位上面的数字,找到对应的那种状态就好。

二、二分法来理解

上面的理解不了,可以看看简单一点的二分法:

还是以1000桶为例,具体喝法如下:
第一只:1-500
第二只:1-250,501-750
第三只:1-125,251-375,501-625,751-875

这还是一个取对数的计算,所以最少需要10只老鼠。


三、代码实现

方法理解了后,那就可以直接上代码了,就是一个算对数的程序,对于输入酒的桶数n,需要 log_{2}n(向上取整) 只小白鼠。

主要注意,c语言中 log 函数默认以e为底,计算2为底数需要用到换底公式。

#include<stdio.h>
#include<math.h>

int main() {
    int T, i;
    long long int n;
    double m;
    scanf("%d", &T);
    for (int i = 1; i <= T; i++) {
        scanf("%lld", &n);
        m = log(n) / log(2);   //采用换底公式
        if (m != (int) m)  //若有小数部分,需要进1
            m++;
        printf("%d\n", (int) m);
    }
} 


end 

欢迎关注个人公众号 鸡翅编程 ”,这里是认真且乖巧的码农一枚。

---- 做最乖巧的博客er,做最扎实的程序员 ----

旨在用心写好每一篇文章,平常会把笔记汇总成推送更新~

在这里插入图片描述

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值