因为小尼没对上牌而爆火的春晚魔术《守岁共此时》
- 是否让你想起刚学编程时做的一道题目?
- 为什么刘谦一定要用 7 字咒语,4 张牌?
- 如果换一个咒语,魔术要怎么设计?牌数要变吗?
- 小尼没对上牌的根本原因是什么?
如果有这些疑问,本文将为你揭晓。
了解一样东西最好的方法就是将它重新做一遍,在了解底层数学逻辑之前,我们先尝试换个咒语,从头设计一个同款魔术。
从头设计一个同款魔术
第一步 编个咒语
编个神秘的咒语,字数务必是2的幂次减1。
刘谦的咒语是"见证奇迹的时刻",7个字,是2的三次方减1。
我也编一个咒语 "龙年大吉,快关注技乐书香,好运不断" 总计15个字,为2的四次方减1。
第二步 算牌数
需要的牌数为比咒语字数小的最大的2的幂次。
刘谦的咒语7个字,比7小的最大2幂次为4(2的平方),所以刘谦用了4张牌。
我的咒语为15个字,比15小的最大2幂次为8(2的三次方),所以我需要用8张牌。
第三步 关键魔术步骤
以下四步为最为关键魔术步骤:
- 步骤一:8张牌(上一步算出的牌数)对折撕开,堆到一起,撕开后的牌堆共16(
2*8
)张(半)牌 - 步骤二:将牌顶7(=8-1)张牌插入中间经过这一步,堆顶和堆底就是配对的两张牌了
- 步骤三:将堆顶的牌藏到屁股底下此时堆里还剩15张牌
- 最后一步:留一张(从堆顶移动到堆底),丢一张,循环往复直到剩下最后一张牌。其实最后一张就是堆底,刚好和屁股底下的牌凑一对(为什么剩下的最后一张刚好就是堆底?具体原因在下一章节的数学逻辑中再详解)
为什么在最关键的四步中都没有看见咒语?下面将会用到
第四步 设计丢牌条件
为了体现"共此时"的理念,在步骤三和最后一步之间再按条件丢些牌。
- 步骤四:按条件丢牌,只要丢的牌数小于等于7(=8-1,8为之前算出的牌数),可以随意设计,越神秘越好,比如
- 男生扔一张
- 女生扔两张
- 单身再扔一张
- 步骤五:念咒语,每念一个字将牌从堆顶移动到堆底
咒语的作用就是消除丢牌对最后一步的影响,保证丢不同数量牌后,最后一步剩下的始终是堆底的那张。(具体原理在下一章节的数学逻辑中再详解)
第五步 再添加些没什么用的神秘步骤
步骤一和步骤二之间可以将任意数量的牌从堆顶移动到堆底。因为此时只要相对顺序不变,不影响最后结果。刘谦魔术的第一步就是按名字字数把牌从堆顶移到堆底,这一步其实没啥用。很多人说小尼失误是因为名字太长了,放错了,显然是不对的,即使数量错了,也不会影响魔术最终的成功。
步骤三和念咒语之前可以将任意数量的牌从堆顶移动到中间。因为此时底牌最重要,只要底牌不变就行。刘谦在藏牌到屁股底下后,让大家根据南北方人的不同,从牌顶往中间插不同数量的牌。小尼的失误其实就在这一步,他没有把牌插入中间而是放在了底部,把最为关键的底牌给换了,导致最后没有配对上。
恭喜你,此时你就拥有一个和刘谦咒语不同的 《守岁共此时》同款魔术了。相信你也准备好了更加烧脑的环节,理解魔术背后的数学逻辑。
魔术背后的数学逻辑
约瑟夫环:程序员的回忆杀
当刘谦在最后一步说 "好运留下来,烦恼丢出去" 时,立即就唤醒了我本科学习 Java 的记忆。在 "Java程序设计" 专业课期末考试的最后一题就是,"n个猴子围成一圈不断报数,奇数留下来,偶数淘汰,求最后留下来的猴子是第几个" 。估计不少程序员在初学编程时,都接触过类似的题目。leetcode 上这个题目叫做 破冰游戏。
我这样的小菜鸡一般就写两个 for 循环模拟整个过程,大约几十行代码,沾沾自喜。然后惊讶地大神一行代码就解决了:
java
// n 为总数
// highestOneBit 是 Java 内置的方法,用于求小于等于 n 的最大2次幂
2*(n - Integer.highestOneBit(n))+1
也就说存在这么一个简单的公式,最后留下来的猴子编号,为 2*(n-小于等于n的最大2次幂)+1
:
- 2 个猴子,小于等于 2 的最大2次幂为 2,则最后留下来的为第
2*(2-2)+1=1
个 - 7 个猴子,小于等于 7 的最大2次幂为 4,则最后留下来的为第
2*(7-4)+1=7
个 - 15 个猴子,小于等于 15 的最大2次幂为 8,则最后留下来的为第
2*(15-8)+1=15
个
这是一个经典的数学问题,叫做"约瑟夫环"。这个公式乍看下比较神奇,细想一下其实很直观。 当猴子的数量为 2 的幂次时,每轮报数都会刚好减少一半并且回到第一个猴子,第一个猴子报的永远都是奇数,所以最后剩下的只能是第一个猴子:
对于一般情况,当有 (n-小于n的最小2次幂)
的猴子被淘汰时,就又回到了特殊情况,而且等价于特殊情况中1号猴子的,就是 2*(n-小于n的最小2次幂)+1
号:
构造方程
有了约瑟夫环的公式,我们就可以根据两个条件构造方程:
- 根据操作步骤,推断最后剩下的牌是第几张,假设这个数字为 k
- k 还必须为牌堆最后一张,因为我们将配对的牌留在了最底下
假设我们的牌数为 n,则撕开后的牌数为 2n ,因为我们会藏一张牌,所以参与约瑟夫环的牌就只有 2n-1
张, 假设 2n-1=2^m+b
。我们按照核心步骤推演一下 k
- 步骤四会按条件丢牌,但是我们约定丢的牌数是不会超过 b 的,假设丢的牌数为 a ,所以最后参与约瑟夫环的牌数为
2n-1-a
,可以写作2n-1-a=2^m+b-a
。 - 步骤五念咒语,假设咒语字数为 c ,会导致牌往前移动 c 位,因此约瑟夫环的结果需要
+c
才是牌原本的位置。 - 最后一步约瑟夫环,参与的牌数为
2^m+b-a
, 套用公式为2(b-a)+1
,再修正下咒语的影响,因此k=2(b-a)+1+c=c+1+2b-2a
同时 k 还必须是牌堆的最后一张,因为此 k 必须是参与约瑟夫环牌数的整数倍,即 2n-1-a=2^m+b-a
的整数倍 。其中 a 是观众按条件任意丢弃的,无法控制,而 b 跟牌数相关,c 是咒语字数,都是魔术师可以控制的,因此我们要做到与 a 无关。从 k 的公式中我们可以看到 -2a
,因此只能是 2^m+b-a
的 2 倍,进而列出本文最重要的方程:
化简得
因此咒语字数必须为2的幂次减一。根据前面的定义 2n-1=2^m+b
,我们可以进一步求得牌数( 2^m
我们可以理解成比 c 小的最大的2的幂次):
b 必须小于 2^m
,大于0,因此可以求得一个范围:
- 下限:b 取 1 时,
n=2^(m-1)+1
- 上限:b 取
2^m-1
时,n=2^m
,即 n 为比 c 小的最大的2的幂次
所以对于刘谦用 7 字咒语,3张牌也是可以的,但是按条件丢牌的牌数是不能超过 b 的,b 取得太小,步骤四的按条件丢牌就会受到限制。如果刘谦用3张牌,那么丢牌的数量就能超过 1,"男生丢一张,女生丢两张"这个设计就要翻车了,刘谦还是必须要用 4 张牌。
为了给丢牌留最大的空间,b 我们直接取最大值,此时的牌数刚好就是比 c 小的最大的2的幂次,丢牌的最大数量b就是牌数减1。
总结
思考了很久,但是最后的结论却非常简洁,这或许就是数学的魅力吧:
End
感谢阅读,希望本文能让您有所收获。文中的结论,除了经典的约瑟夫环理论外,其余方程都为笔者自行推导,引用请注明出处。如果看完还有困惑之处,也欢迎评论本文和我进一步交流,有问必回。
作者:元青
微信公众号 「技乐书香」