首先祝大家新年快乐,龙年大吉!相信大家都对昨晚春晚的魔术记忆深刻(即使没看过,也一定看到了尼格买提穿帮的新闻)。接下来就来模拟一下昨晚刘谦的魔术,并扒扒其原理。
1.魔术模拟
Step 1:假设有四张牌,编号为 1、2、3、4

Step 2:四张卡牌对折撕开,并重叠

Step 3:根据自己名字的字数下放牌;例如名字字数为 2,则序列如下所示

Notice:这是无效操作,并不会对最终效果产生影响。
Step 4:拿起前三张牌,插进剩下牌的中间

Notice:这一步操作,要求插进中间的卡牌数必须为 3,因为两张相同牌的中间隔了 3 张卡牌;经过这样的操作后,能够保证头和尾是相同卡牌;其次插进的位置只要不是头和尾,不会对结果造成影响
Step 5:隐藏第一张卡牌,此时隐藏的卡牌是 2

Step 6:南方朋友拿起前一张卡牌;北方朋友拿起前两张卡牌;自己不知道是南方和北方的朋友拿起前三张卡牌。将这些卡牌插入剩下卡牌的中间。示例拿起一张,结果如下所示

Notice:这一步操作是无效操作;无论拿几张牌,以及插入在哪个位置,只要保证卡牌 2 在最后即可
Step 7:男生拿起前一张卡牌;女生拿起前两张卡牌;将这些卡牌扔掉。两种情况如下所示

Notice:这一步的操作是保证卡牌总数为 6 或 5
Step 8:大喊 “见证奇迹的时刻”,每喊一个字将一张卡牌下方,总共下放 7 次

Notice:这一步是关键操作,其主要目的是为了将卡牌 2 放置到最后一步的约瑟夫环问题的答案位置
Step 9:第一张卡牌将其下放,第二张卡牌将其丢掉;该步骤操作四次

Notice:这一步操作的本质是约瑟夫环问题;其将这一序列排成一个环,为每个卡牌编号(从0开始,如 Step 8 所示),从 0 开始报数,报到 2 的卡牌移除;然后从移除卡牌的下一个卡牌开始重新报数,最后留下的卡牌就是与隐藏卡牌配对的卡牌。
约瑟夫环问题的公式为:
f(n,m)={0[f(n−1,m)+m]%nn=1n>1
其中 n 代表卡牌数,m = 2。
根据公式可知:f(6,2)=4;f(5,2)=2
其结果正好是 Step 8 中卡牌 2 的位置。
代码:
通过 Java 语言实现了模拟程序,其代码已上传至 Github,链接在下方。执行结果如下图所示:

2024 SpringFestivalMagic (github.com)
根据以上的模拟过程是不是已经清楚这个魔术的原理了呢~接下来讨论一下约瑟夫环问题的推导过程。
2.约瑟夫环问题数学推导过程
1.问题描述
假设有 n 个人围成一个圈,根据某一顺序为每一个人设置一个编号 0、1、2、…、n−1。从编号 0 开始报数,报到 m 的人出圈;然后从出圈人的下一个人重新开始报数。重复该操作,直到只剩下一个人,这个人就是赢家。
2.示例说明
假设有 6 个人,报到 2 的人出圈。其执行过程如下所示:

3.数学推导
定义:
- 在序列 0、1、2、…、n−1 执行约瑟夫环操作,最后剩下的编号为 f(n,m)
- 执行完第一次约瑟夫环操作后,出圈的编号为 k,则下一次操作的序列为 k+1、k+2、…、n−1、0、1、…、k−1,在该序列上的约瑟夫环操作,最后剩下的编号为 g(n−1,m)
- 从以上的定义可知,f(n,m)=g(n−1,m)
重新映射:
将执行完第一次约瑟夫环操作后得到的新序列进行重新编号,从 0 开始,则其映射关系如下表所示:
| 原编号 | 新编号 |
|---|
| k+1 | 0 |
| k+2 | 1 |
| … | … |
| n−1 | n−k−2 |
| 0 | n−k−1 |
| 1 | n−k |
| … | … |
| k−1 | n−2 |
根据以上的映射关系,可以总结得到以下的公式:
h(x)={x−k−1x+n−k−1k+1 <= x <= n-1 0 <= x <=k-1
∵k+1≤x≤n−1 → 1≤x−k≤n−k−1 → 0≤x−k−1≤n−k−2
n≤x+n−k−1≤n+n−k−2
∴n%n=0≤(x+n−k−1)%n≤(2∗n−k−2)%n=n−k−2
∴式子(x+n−k−1)%n,当 k+1≤x≤n−1 时,0≤h(x)≤n−k−2
∵0≤x≤k−1 → n≤x+n≤n+k−1 → n−k≤x+n−k≤n−1
n−k−1≤x+n−k−1≤n−2
∴(n−k−1)%n=n−k−1≤(x+n−k−1)%n≤(n−2)%n=n−2
∴式子(x+n−k−1)%n,当 0≤x≤k−1 时,n−k−1≤h(x)≤n−2
∴可将公式统一为 h(x)=(x+n−k−1)%n
为了消除求余号 % ,可以引入正整数 T,以达到求余的效果;公式如下所示:
h(x)=(x+n−k−1)%n=(x+n−k−1)+(T−1)n=x−k−1+Tn
推导:
∵f(n−1,m) 是在重新映射后的序列上的约瑟夫环操作结果
∴f(n−1,m)=h(g(n−1,m))
∴g(n−1,m)=h−1(f(n−1,m))
∵x=h(x)+k+1−Tn
∴h−1(x)=x+k+1−Tn=(x+k+1)%n
∴g(n−1,m)=h−1(f(n−1,m))=[f(n−1,m)+k+1]%n
∵k 是第一次约瑟夫环操作的结果,可知 k=(m−1)%n
∴g(n−1,m)=[f(n−1,m)+(m−1)%n+1]%n=[f(n−1,m)+m]%n
∵f(n,m)=g(n−1,m)
∴f(n,m)=[f(n−1,m)+m]%n
代码:
public int findWinner(int n,int m){
if(n==1) return 0;
int winner = findWinner(n-1,m);
return (winner+m)%n;
}
3.参考文献