【问题标题】:Solving yes-no test解决是-否测试
【发布时间】:2014-06-29 23:59:18
【问题描述】:

有一个带有N YES 或NO 问题的测试。你可以写测试,教授会告诉你你的答案有多少是正确的。最快通过考试的方法是什么? IE。用最少的试验次数正确回答所有问题。

UPD 使用N+1 试验的解决方案是显而易见的。在每次试验中,我们都会有一个问题的正确答案。它是 1 位信息。但是教授每次都给我们一个从 0 到 N 的数字,它是 log2(N + 1) 位信息。这就是为什么最好的解决方案具有O(N / log(N)) 的复杂性。 我正在寻找任何具有亚线性最差时间复杂度的解决方案。

【问题讨论】:

  • 基本上是一个probability 问题---你不认为它适合math.stackexchange.com!
  • 你可能是对的,但我认为它也非常适合algorithm 标签:)
  • 通过测试听起来像是在玩Mastermind。 (请务必点击链接,文章包含“算法”部分。)
  • @DanielDaranas 这不是概率,应该有一个严格的算法,具有最差的性能情况和有效的复杂度。
  • Erdös 和 Rényi 在 1963 年解决了这个问题:renyi.hu/~p_erdos/1963-12.pdf

标签: algorithm


【解决方案1】:

相对于 N+1 方案的明显改进:

从所有 Y 答案开始。

然后我们确切地知道有多少是/否。

p 是在任何给定位置上获得肯定的概率。 p >= 1/2 不失一般性。

然后我将显示平均2 - p^2 尝试的两个第一个答案。

我改变了前两个问题的答案。 至少p^2 我会知道他们两个的确切答案。如果不是 - 那么我至少知道其中一个是 Y,另一个是 N,我需要再问一个问题。

所以在最坏的情况下p = 1/2 我需要1 + N * 7/8

【讨论】:

  • 是的。而且只是假设教授在试验期间没有改变答案。否则他总是可以对我选择的任何一对返回是/否。
  • 当然教授不会改变答案,是我们在作弊:)
【解决方案2】:

在来自 Carsten 评论的 article 中,Erdös 和 Rényi 展示了如何将问题表述为找到最小数量的测试序列,这些测试序列一起可以为未知序列生成唯一哈希。由于他们的示例显示了一个通过四个测试解决了五位数的序列,因此我试图为长度为六和七的序列提出一个次线性的测试数。

看看 Erdös 和 Rényi 的例子,并受到 Ioannis 提到的“分而治之”的启发,我认为测试序列可能是一种划分然后再细分序列。尝试了几次才能获得序列长度为 7 的有效测试。

考虑您所要求的算法的一种方法可能是泛化/自动生成这些测试序列的方法。

下面的 JavaScript 程序将测试序列与给定长度的所有序列进行比较,并对一致数字的数量进行哈希处理。如果两个不同的序列生成相同的哈希,程序会通知找到了匹配项,这意味着这种测试组合将不起作用。如果未找到匹配项,则表示哈希是唯一的,测试应该可以工作。

// http://resnet.uoregon.edu/~gurney_j/jmpc/bitwise.html
function setBits(i)
{
     i = i - ((i >> 1) & 0x55555555);
     i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
     return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}

// http://stackoverflow.com/questions/10073699/pad-a-number-with-leading-zeros-in-javascript
function pad(n, width, z) {
  z = z || '0';
  n = n + '';
  return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}

function numCoincidences(a,b){
    var n = 0
    for (var i=0; i<a.length; i++){
        if (a.charAt(i) == b.charAt(i)){
            n ++
        }
    }
    return n
}

var sequenceLength = 6

var tests = [
        "111111",
        "111000",
        "010010",
        "011001",
        "000100"
    ]

/***
var sequenceLength = 7

var tests = [
    "1111111",
    "1111000",
    "0100100",
    "0110010",
    "0110001",
    "0001000"
]
***/

var hash = {}

console.log("       " + tests.join(" "))

for (var i=0; i<1<<sequenceLength; i++){
    if (setBits(i) < Math.floor(sequenceLength / 2)){
      var tmp = pad(i.toString(2),sequenceLength)
      var h = ""
      for (var j in tests){
        h += numCoincidences(tests[j],tmp)
      }
      console.log(tmp + "   " + h.split("").join("      "))
      if (hash[h]){
          console.log("found match")
      } else {
          hash[h] = true
      }

    }
}

console.log("done")

输出:

"       111111 111000 010010 011001 000100" <-- test sequences
"000000   0      3      4      3      5"
"000001   1      2      3      4      4"    <-- sequences to match, followed by
"000010   1      2      5      2      4"             the number of coincidences 
"000011   2      1      4      3      3" 
"000100   1      2      3      2      6" 
"000101   2      1      2      3      5" 
"000110   2      1      4      1      5" 
"000111   3      0      3      2      4" 
"001000   1      4      3      4      4" 
"001001   2      3      2      5      3" 
"001010   2      3      4      3      3" 
"001011   3      2      3      4      2" 
"001100   2      3      2      3      5" 
"001101   3      2      1      4      4" 
"001110   3      2      3      2      4" 
"010000   1      4      5      4      4" 
"010001   2      3      4      5      3" 
"010010   2      3      6      3      3" 
"010011   3      2      5      4      2" 
"010100   2      3      4      3      5" 
"010101   3      2      3      4      4" 
"010110   3      2      5      2      4" 
"011000   2      5      4      5      3" 
"011001   3      4      3      6      2" 
"011010   3      4      5      4      2" 
"011100   3      4      3      4      4" 
"100000   1      4      3      2      4" 
"100001   2      3      2      3      3" 
"100010   2      3      4      1      3" 
"100011   3      2      3      2      2" 
"100100   2      3      2      1      5" 
"100101   3      2      1      2      4" 
"100110   3      2      3      0      4" 
"101000   2      5      2      3      3" 
"101001   3      4      1      4      2" 
"101010   3      4      3      2      2" 
"101100   3      4      1      2      4" 
"110000   2      5      4      3      3" 
"110001   3      4      3      4      2" 
"110010   3      4      5      2      2" 
"110100   3      4      3      2      4" 
"111000   3      6      3      4      2" 
"done"

【讨论】:

  • 1.我以为 Erdös 正在处理任意的N? 2. 你声称给定的 5 次试验序列将解决 N=6 的试验,因为表中的所有行都是不同的,对吗? 3. 你是怎么找到这个序列的?蛮力? 4. 对于任意N,该序列将持续多长时间?
  • @Mikhail Erdös 和 Rényi 的文章中的数学对我来说太高级了。我假设他们的函数B(n) - 所需的测试序列的数量会随着n 变大而相对于n 减少,但我可能是错的,对于一些任意大的n 它可能又是等于n。一般来说,他们似乎证明了B(n) &lt;= n;如果我理解正确,B(n) 倾向于2n / log n 足够大的n
  • @Mikhail 2. 是的,我声称给定的 5 次试验序列将解决 N=6 的测试,因为表中的所有行都是不同的 - 显然我们对每个未知序列都有一个唯一的哈希长度为六。 3. 我通过反复试验找到了测试 - 我在答案的第二段中描述了我的想法。
【解决方案3】:

免责声明:我不知道这是否是最快的方式。我敢肯定,在特定场景中,您可以通过更少的试验来摆脱困境,但这可能是一个严格的上限(最坏的情况)。

按照您的喜好填写第一轮,只要记住您的选择即可。如果您愿意,可以为所有选项选择“否”。

在下一次试用中,仅更改第一个答案(按照示例:选择是)。根据答案的变化,您将知道第一个问题的正确答案(如果结果增加,则您给出正确答案,否则为错误答案)。

现在只更改第二个,依此类推。

您需要 N+1 条路径。

【讨论】:

    【解决方案4】:

    这是另一种方法(分而治之)。为简单起见,我将引用 N 的特定值,但概括起来很简单。

    N=10 假设在第一轮 (trial=1) 我们正确回答了5 问题。这是最有可能的结果,并且揭示的信息量比我们注意到的要少。

    那么下面的逻辑允许我们不检查每一个数字:

    • 将答案列表分成两组,1...56...10。给定我们有 5 个正确答案,每组可能的正确答案是

      (0,5)
      (1,4)
      (2,3)
      (3,2)
      (4,1)
      (5,0)
      

    现在,为下一次试用翻转答案1...5。那么上面的状态变化如下:

        (0,5)--> (5,5)   -- 10 correct
        (1,4)--> (4,4)   -- 8 correct
        (2,3)--> (3,3)   -- 6 correct
        (3,2)--> (2,2)   -- 4 correct
        (4,1)--> (1,1)   -- 2 correct
        (5,0)--> (0,0)   -- 0 correct
    

    如果老师说100 我们完成了,或者我们需要再试一次。无论如何,根据老师说的数字,我们可以知道每个区间有多少正确答案。然后我们可以决定。

    • 2 正确:我们回溯(回溯)前 5 个答案,我们知道我们在答案 1...56...10 中分别有 (4,1) 个正确答案。所以我们也翻转6...10 并得到 8 正确,见下文。
    • 4 正确:与 2 正确一样,我们翻转前 5 个答案,同时翻转 6...10 并得到 6 正确,见下文。
    • 6 正确:我们需要进一步划分和迭代。对于1...56..10,我们最多需要多 3 个步骤,所以总共需要 8 个步骤,包括前两个步骤。
    • 8 正确:我们再次应用二分搜索。我们划分两个初始集合中的每一个(例如,1...5 被划分为1...34...5)并寻找错误的答案。如果它在4...5我们需要两个步骤,否则我们需要三个步骤。因此,再次一共有 8 个步骤。

    【讨论】:

      【解决方案5】:

      天真的解决方案将涉及 O(N) 次尝试:从所有答案开始是 YES,然后在每个 ith 尝试翻转 ith 答案。如果你的分数增加了,保持它;如果没有,请将其翻转回来。递增i,重复。

      更有效的解决方案可能涉及一个非常简单的遗传算法,其中启发式是教授的答案,而变异可能相当于简单地翻转所有答案。这可能会接近 O(log N) 次尝试,但乘法常数当然会更大(如果我不得不猜测的话,至少要大一个数量级),所以它只适用于大 N。

      【讨论】:

        【解决方案6】:

        一些简单的 O(n) 算法的 Python 代码:

        import random
        
        def ask_prof(A, M): return sum(x == y for x, y in zip(M, A)) 
        
        N = 10
        A = [ random.randint(0, 1) for x in range(N) ]
        K, s = [], 0
        for trial in range(N):
            M = K + [ 0 for x in range(N - len(K)) ]
            s1 = ask_prof(A, M)
            M[len(K)] = 1 
            s2 = ask_prof(A, M)
            if s1 < s2: K.append(1)
            else: K.append(0)
        print 'answers are', K
        

        【讨论】:

          猜你喜欢
          • 2019-06-30
          • 2011-10-26
          • 2016-09-09
          • 2015-09-22
          • 2018-09-04
          • 1970-01-01
          • 2015-01-25
          • 2022-06-10
          • 1970-01-01
          相关资源
          最近更新 更多