春晚的魔术实际上是一个约瑟夫问题,最终的结果是魔术开始时确定的几个变量确定好的,扑克牌只是道具和障眼法。网上一查这个问题发现颇有历史渊源,17世纪的法国数学家加斯帕在《数目的游戏问题》中讲了这样一个故事:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行直到仅余15个人为止。问怎样排法,才能使每次投入大海的都是非教徒。
编程实现这个过程:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
int last_one(int n, int m)
{
int res = 0;
if (n == 1) {
return 1;
}
for (int i = 2; i < n + 1; ++i) {
res = (res + m) % i;
}
return res + 1;
}
int main(void)
{
int n, m, i, kill = 0, t = 0, s = 0;
scanf("%d %d", &n, &m);
printf("%s line %d, total object %d, kill every %d object.\n",
__func__, __LINE__, n, m);
// the first object are reserved, the first index start from
// 1, end to n.
bool *array = malloc(sizeof(bool) * (n + 1));
if (array == NULL) {
printf("%s line %d, alloc object buffer failure.\n",
__func__, __LINE__);
return -1;
}
for (i = 0; i < (n + 1); i ++) {
array[i] = false;
}
do {
++t;
if (t > n)
t = 1;
if (!array[t])
s++;
if (s == m) {
s = 0;
printf("%d ", t);
// kill this object.
array[t] = true;
kill++;
}
} while (kill != n); // all has been killed.
printf("kill %d object, last one is %d.\n", kill, last_one(n, m));
free(array);
return 0;
}
可见杀戮顺序是9 18 27 6 16 26 7 19 30 12 24 8 22 5 23 11 29 17 10 2 28 25 1 4 15 13 14 3 20 21,所以只要保证9 18 27 6 16 26 7 19 30 12 24 8 22 5 23位置上的人都是非教徒,则15名教徒能够全部存活下来。
所以,只要把非教徒安排在5,6,7,8,9,12,16,18,19,22,23,24,26,27,30位置上,能够达到仅杀死非教徒的目的。
参考文章
约瑟夫环问题(链表 + 公式)-CSDN博客