最考驗換位思考的一道演算法題

老姚發表於2018-04-19

最近在本站沸點上看到網友(@貓D)的一道數學題。仔細分析後,真心覺得那叫一個難啊,腦子很容易枯竭。。。

題如下:

有兩個大於1的整數 x 和 y,甲知道二者的積,乙知道二者的和。
甲:我不知道這倆數是什麼!
乙:我不知道這倆數是什麼,我就知道你不知道!
甲:現在我知道了。
乙:我也知道了。
請問這兩個數是幾?

第一眼看去,什麼鬼?

想了好久,終於想明白了,本文準備用JS解決它。





(手動分割線,確定馬上知道答案嗎?)







我們先看看正確答案:4 和 13。

我們驗證一下這個答案對不(只是驗證!),順便理解一下情境!

第一次換位思考

因為正確答案是 4 和 13,那麼甲知道的數字是52,甲又知道 52 可以寫成 2 * 26 或 4 * 13 這兩種可能。因此甲不知道這倆數具體是哪一種可能。

乙知道的數字是 17,但 17 的和式分解(我造的詞兒)可能有 2 + 15, 3 + 14,  4 + 13, 5 + 12,  6 + 11, 7+ 10 和 8 + 9 這幾種可能。此時關鍵的資訊來了,乙確信甲不知道答案。這是乙進行第一次換位思考, 思考的過程是這樣的:

假如答案是 2 和 15 的話,那麼甲知道的是 30,但 30 又可以分成 5 * 6 和 3 * 10,因此甲沒法確定答案。同理,乙驗證 3 和 14、4 和 13、5 和 12、6 和 11、7 和 10、8 和 9 等其餘可能,推理出甲沒法知道正確答案。因為它們的乘積的因式分解都不唯一。

第二次換位思考

因為此時乙已經告訴了甲,這一個關鍵資訊,甲也可以反向推理了:

假如是 2 和 26 的話,那麼和是 28。而28的和式分解之一是 5 + 23,那麼乙會驗證 5 * 23的因式分解是否唯一,又因其恰好為倆素數,分解必然唯一。那麼乙就不會斷定我不知道答案了。因此 x 和 y 不能是 2 和 26,則是 4 和 13。到這裡,想必思路還很清晰吧。

第三次換位思考

乙一看甲竟然通過自己的話得到了答案,他進行了一次較複雜的推理,容我慢慢說來,可要仔細看好,沒講明白的話,多看兩遍,有點燒腦子,糊了!

此時,乙會站在甲的角度去想這個問題:

甲能得到正確答案,肯定是除了正確答案以外,排除掉了其他所有可能。排除條件就是:只有正確答案的和的和式分解的對應乘積的因式分解全不唯一這個條件是唯一的,即我之前告訴他的,“我就知道你不知道”。

比如,看 2 和 15 是否是答案嗎,其乘積是 30,而 30 的另一個因式分解之一 5 * 6 的和是 11,並且 11 的所有和式分解 2 + 9,3 + 8,4 + 7,  5 + 6 的乘積因式分解也都不唯一。這樣的話,如果乘積真是30,到底 2 和15是正確答案呢,還是 5 和 6才是正確的答案呢,這樣甲沒法抉擇的。因此 2 和 15不是答案。同理驗證,除了 4 和 13 這一對兒外,其餘可能都不能使甲得出答案。

我們的換位思考

(上面只是驗證答案,下面我們要求出 4 和 13來。)

說到換位思考,要注意,這道題裡還有影之第三人,就是:“你”!

他倆最起碼,一人知道積是 52,另 一人知道和是 17。可我們讀者呢,那是倆眼一抹黑啊,可不知道二人知道的數字。讀者掌握得資訊要比他二人少,所以要想得正確答案難度倍增。

我們的換位思考就是想法依次從甲乙的話裡,去過濾出正確答案。

終於要寫程式碼啦!!!

我們不妨假設 x <= y。為了減少計算量,二者都小於 100 吧。

首先我們先算 x 和 y 的所有可能:

const max = 100;
let sums = {};
let products = {};

for (var i = 2; i < max; i++) {
  let x = i;
  for (var d = 0; d < max - x; d++) {
    let y = x + d;

    let p = x * y;
    products[p] = products[p] || [];
    products[p].push({ x: x, y: y });
    
    let s = x + y;
    sums[s] = sums[s] || [];
    sums[s].push({ x: x, y: y });
  }
}
複製程式碼

products 是所有數的乘積的因式分解集合。比如 products[12] 的值是 [{x: 2, y: 6}, {x: 3, y: 4}]。類似,sums 是所有數的和式分解。

甲:我不知道答案。

對應這句話,我們要在 products 中過濾掉因式分解唯一的情況:

for (let key in products) {
  if (products[key].length == 1) {
    delete products[key];
  }
}
複製程式碼

乙:我不知道答案,

同理,我們也要在 sums 中過濾掉和式分解唯一的情況:

for (let key in sums) {
  if (sums[key].length == 1) {
    delete sums[key];
  }
}
複製程式碼

乙:我就知道你不知道!

對於任意一可能和值,其每種和式分解對應的乘積都該有多種可能分解形式。我們過濾掉 sums 中不滿足的情形:

for (let key in sums) {
  let pairs = sums[key];
  let flag = pairs.every(function(pair) {
    return (pair.x * pair.y) in products;
  });
  if (!flag) {
    delete sums[key];
  }
}
複製程式碼

甲:現在我知道了。
乙:我也知道了。

根據乙最後換位思考的過程,我們遍歷sums的每一個key。再根據排除條件,即,和式分解對應的積的因式分解是否唯一,來確定可能的結果。

for (let key in sums) {
  let pairs = sums[key]
  let r = pairs.filter(function(pair) {
    let ps = products[pair.x * pair.y];
    let r = ps.filter(function(p) {
      return (p.x + p.y) in sums
    })
    if (r.length == 1) {
      return true
    }
    return false
  });
  if (r.length == 1) {
    console.log(r)
  }
}
複製程式碼

寫到這裡,算是完事了,也不知道我寫明白了沒有。。。確實有點繞,算得也比較笨。。

另外,程式碼還有點小問題,ps.filter 是取巧做的。算是求出了 4 和 13 這兩個值了。當然,只限於小於100的數,也只是驗證了存在性。唯一性恐怕需要數學去證明了,哥德巴赫猜想?

寫此文的過程,想起了當年看博弈論時的情景,腦仁兒疼!

最後給出完整demo地址


相關文章