zoukankan      html  css  js  c++  java
  • 【每天进步一点】毒药和老鼠的研究

    之前碰到过毒药和老鼠,鸡蛋和称的问题,每次都拿笔在纸上推敲很久,这类问题今天终于有了完整的解决思路。

    基础:

    1.整数的二进制表达式

    1000的二进制表达式是什么呢?

    1000/2=500  --(余)--0
    500/2=250   --(余)--0
    250/2=125   --(余)--0
    125/2=62    --(余)--1
    62/2=31     --(余)--0
    31/2=15     --(余)--1
    15/2=7      --(余)--1
    7/2=3       --(余)--1
    3/2=1       --(余)--1
    1/2=0       --(余)--1

    1000的二进制表达式为 1111101000 = 29 + 28 + 27 + 26 + 25 + 23 = 512 + 256 + 128 + 64 +32 + 8

    还需要其他的吗?不需要了,有上面的基础足矣。

    二进制和上面的题目有什么关系呢?

    在计算机基础里面,函数B2Uw表示Binary to Unsigned(长度为w的二进制到无符号整数),它能够被定义为一个映射:

    B2Uw{0,1}w  --> {0,1,...,2w-1}

    也就是说表达0~2w-1的整数值,只需要w位的二进制即可。

    再转换一下:w位的二进制可以表达(2w-1)+1=2w个整数值

    反应在我们的命题中,n个瓶子对应n个可能的结果,转换成二进制之后,只需要x位的0101就可以表达出来了。(n已知,x未知。x的求值参考上面的结论)

    命题:8瓶水,其中一瓶有毒,中毒的老鼠会在一个星期之后死亡。给你一个星期,请问最少几只老鼠可以测试出哪瓶有毒?

    步骤一:确定需要几只老鼠

    8 = (二进制)1000 = 23

    即三位的二进制即可表达8种情况。

    步骤二:为药品分配准备数据

    将三位的二进制所有的情况罗列出来,剔除0的情况

    计算机的整数(索引)从0开始,生活中的计数习惯从1开始。而实际的操作中,我们可以拿出一瓶不做测试,当所有老鼠都存活下来时,就说明拿出来的这一瓶是最后的结果。这里可以取巧,将0理解为索引,映射到最后一个数字,在这里就是8;剔除0,也就是剔除了第8瓶水的情况。

    001//1010//2011//3100//4101//5110//6111//7

    从右往左,第一列对应的是1010101,其中1对应的数即为{1,3,5,7};第二列对应0110011=>{2,3,6,7};第三列对应0001111=>{4,5,6,7}

    步骤三:提取上面的数字,得出喂食方案。

    第一只老鼠给他喂食(1,3,5,7)瓶水的混合液,第二只老鼠喂食(2,3,6,7)的混合液,第三只老鼠喂食(4,5,6,7)的混合液。

    步骤四:根据实验结果推算答案。

    如果第一只死了,第二只和第三只活着,我们依旧从右往左写成001(死掉为1,活着为0),转成十进制整数为1。那么结论就是第1瓶水有毒。

    如果第一只死了,第三只也死了,第二只活着。二进制为101,转十进制为5。那么结论就是第5瓶水有毒。

    如果全部死亡。111,对应7,那么就是第7瓶水有毒。

    如果全部存活。000,那就是0瓶水(也就是上面预留的第8瓶水)有毒。

    步骤五:验证答案。

    101的情况:第二只存活,说明2,3,6,7没有问题,而第一只和第三只,取他们的交集,有可能的是5和7,但是前面已经排除了7,所以答案是5。

    再来验证110的情况:第一只存活,二、三只光荣了,说明1,3,5,7没问题,后两只的混合液取交集,有可能是6,7。前面排除了7,答案只能为6。

    继续思考:

    1000瓶水,找出其中有毒的那一瓶水,需要多少小白鼠,实际操作怎么样呢?

    29 < 1000 < 210 所以需要的白老鼠个数为10只,也就是10只老鼠才能充分的反应1000种情况。当然,10老鼠最多可以反映多少情况呢,应该是210=1024种。

     

    得出了需要十只老鼠,接下来十只老鼠应该怎么操作才能得出结论呢?

    0000000001 ------1

    0000000010 ------2

    0000000011 ------3

    0000000100 ------4

    0000000101 ------5

    ...

    1111100111 ------999

     

    那么第一只老鼠应该喝1,3,5,7,9,11,...,997,999瓶水的混合液

    第二只老鼠则是2,3,6,7,...,998,999瓶水的混合液

    第三只老鼠则是4,5,6,7,12,13,14,15,...,996,997,998,999瓶水的混合液

    ...

    第十只老鼠则是512,513,514,...,999瓶水的混合液

    好吧,每个老鼠的指标都是接近500瓶的混合液,这也是自然的,每个位上有0,1两种可能。这么多,老鼠估计喝都喝死了。还想到一个问题啊,稀释这么严重,能毒死老鼠吗?这年头老鼠的生命力很顽强啊,这得上好的毒药啊......

     

    ......最后观察老鼠的牺牲情况,从右至左,死掉为1,活着为0,得到最终的二进制表达式,然后转换成十进制,即为有毒瓶子的答案。

    注意:这里1000<210的情况,也可以将1111101000 -----1000的情况列出来。最后如果第四、六七八九十只老鼠死了的话,就可以判断是第1000瓶水有毒。但是也可以将1000空出来,不喂食任何老鼠。最后没有老鼠死掉的话,也可以判断是第1000瓶水有毒了。这样可以避免老鼠的牺牲,阿弥陀佛~~~~

    算法总结:

    根据园友Wossoneri的提示,仔细看了汉明码的校验方法,看到如下说明:

    可发现一个比较直观的规律:第i个检验位是第2i-1位,从该位开始,检验2i-1位,跳过2i-1位……依次类推。例如上表中第3个检验位p4从第23-1=4位开始,检验4、5、6、7共4位,然后跳过8、9、10、11共4位,再检验12、13、14、15共4位……

    由上面的思路写了下面的代码

    计算小白鼠数量:

     Console.WriteLine("请输入总瓶数:");
     int poisonNum = int.Parse(Console.ReadLine());
     var x = Math.Log(poisonNum, 2);
     var mouseNum = (int)x + (x % 1 > 0 ? 1 : 0);
     Console.WriteLine("需要小白鼠数量:" + mouseNum);
    View Code

    老鼠喂食方案:

    Console.WriteLine("老鼠喂食方案:");
    List<int>[] solution = new List<int>[mouseNum];
    for (int i = 0; i < mouseNum; i++)
    {
        solution[i] = new List<int>();
        int segment = 1 << i;
        int idx = segment;
        while (idx < poisonNum)
        {
            solution[i].AddRange(Enumerable.Range(idx, segment));
            idx += 2 * segment;
        }
        solution[i].RemoveAll(aa => aa >= poisonNum);
    }
    for (int i = 0; i < mouseNum; i++)
        Console.WriteLine("第{0}只老鼠的喂食方案:{1}", i + 1, string.Join(",", solution[i]));
    View Code

    根据实验结果得出结论:

    Console.WriteLine("假设有毒瓶数为:");
    int poisonOne = int.Parse(Console.ReadLine());
    Console.WriteLine("死掉的老鼠有:" + string.Join(",", solution.Select((aa, idx) => aa.Contains(poisonOne) ? idx + 1 : -1).Where(idx => idx != -1)));
    var resBinary = new string(solution.Select(aa => aa.Contains(poisonOne) ? '1' : '0').Reverse().ToArray());
    Console.WriteLine("二进制表达式为:" + resBinary);
    var res = Convert.ToInt32(resBinary, 2);
    if (res == 0) res = poisonNum;
    Console.WriteLine("测试结果为:第{0}瓶有毒", res);
    View Code

     

    发散的问题:6个质量相等的小球和1个质量稍重的球,不能用天平,只能用称,设计一种方法,只能量三次就找出稍重的球。

    类似的问题,这里数量变成了7个。测试数据也从死掉变成了重和轻,不过一样可以转换成0和1。

    如果你看懂了上面,不妨想想这个的答案。

  • 相关阅读:
    Java正则表达式的语法与示例
    python调用另一个文件中的代码,pycharm环境下:同文件夹下文件(.py)之间的调用,出现红线问题
    开发者应当了解的WebKit知识
    strings和nm命令
    CSS元素选择器 element selector(type selector)
    如何查看本机正在监听的端口
    Error 0x80070020 when you try to start a Web site in IIS 7.0
    Windows cannot find ". Make sure you typed the name correctly, and then try again
    Gitblit从一个服务器,迁移到另外一个服务器
    Firefox访问https的网站,一直提示不安全
  • 原文地址:https://www.cnblogs.com/icyJ/p/Mouse_Poison.html
Copyright © 2011-2022 走看看