最近在本站沸点上看到网友(@猫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地址。