五张牌魔术:24 种排列怎么猜中 48 张底牌
题
一副去掉大小王的 52 张牌。1 号囚犯随机拿到 5 张,必须藏起 1 张,把剩下 4 张按从左到右的顺序摆在桌上;2 号只看这 4 张,就要报出藏起来的那张。两人进房间前可以约定一套策略。猜中释放,猜错处决。
用 24 种排列去对应 48 张可能的底牌,看起来最多只有 50% 的胜率。能保证 100% 猜中吗?
一句话
能。 24 种排列对 48 张底牌看似差一半,但 1 号还能决定藏哪张牌;同花色先定底牌花色,13 点圆环把 12 个点数折成 6 个步数,剩下 3 张的 6 种顺序把步数传过去1。
关键技巧
24 不是全部信号。 固定 4 张明牌时,它们的排列只有 \(4! = 24\) 种;底牌有 48 种可能,只看顺序确实最多覆盖一半。但藏哪张牌由 1 号自己选,5 张里藏 1 张、剩下 4 张再排,理论上有 \(5 \times 24 = 120\) 种动作。2 号看到的也不只是顺序——哪 4 张被留下、第一张是什么,都在传信。
动作多不等于能解码。下面三层把 48 种可能一路压到 1 种,每一层都要求两人提前约定同一套规则。
- 抽屉原理定花色:5 张牌塞进 4 种花色,必有两张同花。1 号把其中一张放最左边,另一张藏起来;2 号看第一张的花色,就知道底牌的花色。未知从 48 种缩到同花色的 12 个点数。
- 13 点圆环把 12 折成 6:同花色 A 到 K 围成 13 格圆环,K 的下一格接回 A。任意两张不同的同花牌,两个方向走的步数相加等于 13;13 是奇数,必有一个方向不超过 6 步。1 号把起点那张放第一位,终点那张藏起来,于是底牌一定在第一位顺时针 1–6 步内。
- 3 张牌的全序把 6 折成 1:两人提前约定全序:先比点数,点数相同再按 ♠ > ♥ > ♣ > ♦ 比花色。剩下 3 张分出小、中、大,6 种排列固定对应 1–6。
解
- 1 号看 5 张,找一对同花牌;选一张当起点,使另一张在顺时针 1–6 步内。起点放第一位,终点藏起来。
- 剩下 3 张按全序排成小、中、大;要传的步数 \(d\) 查下表,按对应排列摆放。
- 2 号看第一位:花色就是底牌花色,点数就是起点。后三张的顺序查表得到 \(d\),从起点顺时针数 \(d\) 步,报出底牌。
| \(d\) | 排列 | 例子(小 = 3♠,中 = 7♣,大 = K♦) |
|---|---|---|
| 1 | 小中大 | 3♠ 7♣ K♦ |
| 2 | 小大中 | 3♠ K♦ 7♣ |
| 3 | 中小大 | 7♣ 3♠ K♦ |
| 4 | 中大小 | 7♣ K♦ 3♠ |
| 5 | 大小中 | K♦ 3♠ 7♣ |
| 6 | 大中小 | K♦ 7♣ 3♠ |
完整算例。 假设抽到 ♥10、♥2、♠3、♣7、♦K2。
- 两张红桃,从 ♥10 顺时针走到 ♥2:J、Q、K、A、2,共 5 步,没超过 6。于是把 ♥10 放第一位,藏 ♥2。
- 剩下 ♠3 < ♣7 < ♦K,即小、中、大;要传 5,查表用「大小中」,摆成 ♦K、♠3、♣7。
- 2 号看到第一位是 ♥10,知道底牌是红桃、起点 10;后三张是大小中,读出 5;从 10 顺时针数 5 步,报出 ♥2。
延伸
- 为什么 13 必须是奇数:圆环长 12 时两个方向可能都是 6 步,起点和终点分不清;13 保证总有一侧不超过 6 步、另一侧至少 7 步。
- 5 张里有 3 张同花:任取其中两张按圆环定起点和终点,剩下 3 张照常编码;第三张同花牌留在明牌里也不影响解码。
- 后三张同点数不同花色:按约定的花色序 ♠ > ♥ > ♣ > ♦ 分大小;全序保证 3 张牌一定能分出严格的小、中、大。
- 24 对 48 的计数错在哪:24 是固定 4 张明牌时的排列数;1 号还能选藏哪张,理论动作 \(5 \times 24 = 120\) 种,2 号看到的是「哪 4 张 + 怎么排」。但动作多不等于能解码,上面的构造才是保证。
- 来源与校正:整理自抖音「小偏导」的 6 分 37 秒视频3;口播把算例说成「7 点红桃 10」,圆环步数也转写错了,本文按画面校正为 ♥10、5 步。完整材料包在本地,不随站发布。
数值验证
枚举全部 \(\binom{52}{5} = 2{,}598{,}960\) 手牌,按上面的策略编码再解码,逐一核对是否能还原藏牌:
from itertools import combinations, permutations
PERMS = list(permutations(range(3)))
def card(r, s): return r * 4 + s
def rank(c): return c // 4
def suit(c): return c % 4
def key(c): return (rank(c), 4 - suit(c)) # 同点数按 ♠ > ♥ > ♣ > ♦
def encode(hand):
by_suit = {}
for c in hand:
by_suit.setdefault(suit(c), []).append(c)
a, b = next(v for v in by_suit.values() if len(v) >= 2)[:2]
d = (rank(b) - rank(a)) % 13
if d > 6:
a, b, d = b, a, 13 - d
rest = sorted((c for c in hand if c not in (a, b)), key=key)
return [a] + [rest[i] for i in PERMS[d - 1]], b
def decode(seq):
start, rest = seq[0], seq[1:]
order = {c: i for i, c in enumerate(sorted(rest, key=key))}
d = PERMS.index(tuple(order[c] for c in rest)) + 1
return card((rank(start) + d) % 13, suit(start))
count = 0
for hand in combinations(range(52), 5):
seq, hidden = encode(hand)
assert decode(seq) == hidden
count += 1
print(count) # 2598960

