利用C语言编程从数学角度揭秘2024春晚刘谦魔术《守岁共此时》

目录

一、魔术《守岁共此时》的步骤

二、揭秘魔术《守岁共此时》

三、数学模型约瑟夫问题(约瑟夫环)

四、编程复现魔术《守岁共此时》

五、程序运行结果


一、魔术《守岁共此时》的步骤

利用C语言编程从数学角度揭秘2024春晚刘谦魔术《守岁共此时》_第1张图片

在今年的春晚里,魔术师刘谦表演了一个和纸牌相关的魔术,其精妙的设计带给观众极高的参与感,今天将从数学的角度感受这一魔术的魅力。刘谦在 2024 年春晚表演的魔术《守岁共此时》的全过程如下所示:

首先打乱四张牌,将四张牌对半分开把其中一半放在另一半下面,然后根据你名字长放对应的牌数到牌底,然后最上面三张牌插在剩下牌中间,位置可以任意但是必须在中间(即不能放在最上面或最下面),然后第一张牌就是我们要和最后牌比对的牌,之后以剩下牌继续,根据你是南方北方或者什么人来输入对应数字,拿对应的牌数放在剩下的牌中间,然后根据男女性别扔掉几张牌,然后上方牌放最下面重复7次,最后一步,最上面牌往下放一张再丢一张,以此重复,最后那张就和之前那张对应。

详细分解后的步骤如下所示:

①任意选四张牌,并把牌洗混。

②把扑克牌对折后撕开,让一堆放在另一堆上面,合并成8张牌。

③名字有几个字就把扑克牌从上面往下挪动几张。

④拿出最上面的三张牌,插入剩下牌的中间(位置随意)。

⑤把最上面的牌拿走放在口袋里。

⑥按照南北方人拿出不同的数量插入中间(南1北2不知道3)。

⑦按性别分别丢弃最上面的牌(男1女2)。

⑧嘴里喊出“见证奇迹的时刻”,每喊一个字,把一张牌从上面拿到最下面。

⑨“好运留下来,烦恼丢出去”,当喊出“好运留下来”时,把最上面的牌拿到最底下;当喊出“烦恼丢出去”时,把最上面的牌丢出去(扔掉),重复步骤直至仅剩一张牌。

⑩如果你步骤正确的话,剩余的半张牌和放在口袋里的半张牌会正好拼成一张牌。

二、揭秘魔术《守岁共此时》

利用C语言编程从数学角度揭秘2024春晚刘谦魔术《守岁共此时》_第2张图片

利用C语言编程从数学角度揭秘2024春晚刘谦魔术《守岁共此时》_第3张图片

三、数学模型约瑟夫问题(约瑟夫环)

在刘谦的魔术中,约瑟夫问题的核心思想被巧妙地应用于控制过程的结果。约瑟夫问题是通过固定间隔来选择和排除序列中的对象,直到只剩一个对象。在这个魔术中,尽管没有直接使用固定间隔的排除方法,但通过一系列精心设计的操作步骤(如牌的折叠、插入、丢弃等),实现了类似的控制效果,即无论参与者如何执行这些步骤,都会以一种预定的方式减少牌的数量,最终留下一张特定的牌。

据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决。Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

为了讨论方便,先把问题稍微改变一下,并不影响原意。问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求:胜利者的编号。

我们知道第一个人(编号一定是(m-1)mod n)出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m mod n的人开始):k,k+1,k+2, ... ,n-2,n-1,0,1,2,... ,k-2,并且从k开始报0,我们把他们的编号做一下转换:k -->0, k+1 -->1,k+2 -->2,...,k-2 -->n-2。

变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗。变回去的公式很简单,相信大家都可以推出来:x'=(x+k) mod n。

如何知道(n-1)个人报数的问题的解?只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况 ,这显然就是一个倒推问题。

思路出来了,下面写递推公式:令 f 表示 i 个人玩游戏报 m 退出最后胜利者的编号,最后的结果自然是 f[n]。递推公式:f[1]=0,f[i]=(f[i-1]+m) mod i,且(i>1)。

根据该数学原理可以将类似的游戏改编成数学题目,比如下面这道改编题目:

利用C语言编程从数学角度揭秘2024春晚刘谦魔术《守岁共此时》_第4张图片

四、编程复现魔术《守岁共此时》

#define _CRT_SECURE_NO_WARNINGS 1 // 忽略掉一些编译器的安全警告,_CRT_SECURE_NO_WARNINGS是一个预编译指令

#include  // 包含标准输入输出的头文件
#include  // 包含Windows操作系统的头文件,其中包含了Sleep函数

// 按照题目要求,将一段长度为len的数组arr中begin到end之间的元素进行反转
void func_back(int* arr, int len, int begin, int end)
{
    // 计算需要进行多少次反转(end - len即为反转的次数)
    int sorts = end - len;

    // 外层循环表示进行sorts次反转
    for (int y = 0; y < sorts; y++)
    {
        // 内层循环表示一次反转
        for (int x = len - 1; x >= 0; x--)
        {
            // 使用异或交换两个数的值,实现交换
            *(arr + x + begin) = *(arr + x + begin) ^ *(arr + x + 1 + begin);
            *(arr + x + 1 + begin) = *(arr + x + begin) ^ *(arr + x + 1 + begin);
            *(arr + x + begin) = *(arr + x + begin) ^ *(arr + x + 1 + begin);
        }
        begin++; // 反转完之后修改起始位置
    }
}

// 打印一段长度为len的数组arr
void print(int* arr, int len)
{
    for (int x = 0; x < len; x++)
    {
        printf("%d ", *(arr + x));
    }
    printf("\n");
}

// 将一段长度为arr_len的数组arr循环左移num位
void number_back(int* arr, int arr_len, int num)
{
    for (int x = 0; x < num; x++)
    {
        int p = *arr;
        for (int x = 0; x < arr_len - 1; x++)
        {
            *(arr + x) = *(arr + x + 1);
        }
        *(arr + arr_len - 1) = p;
    }
}

int main()
{
    // 输出一些提示信息
    printf("——————————★★★★★★★★揭秘2024春晚刘谦魔术《守岁共此时》★★★★★★★★——————————\n");
	Sleep(1000);
	printf("首先,请输入你手中的四张扑克牌(为了方便这里只输入数字的牌)\n");
	printf("(注意,每一个数字之间需要使用空格或者回车符分隔开。):\n");

    // 定义并输入一个长度为8的数组arr,前4个元素存放输入的四张牌,后4个元素用于后续操作
    int arr[8] = { 0 };
    for (int x = 0; x < 4; x++)
    {
        scanf("%d", arr + x);
        *(arr + x + 4) = *(arr + x);
    }
	Sleep(1000);

    // 输出输入的四张牌
    printf("\n你手中的四张扑克牌数字是:\n");
	Sleep(1000);
    print(arr, 4);
	Sleep(1000);

    // 第一步,将数组对折并叠放在一起
    printf("\n第一步,你需要将其对折撕成两半,并叠放在一起,结果如下所示:\n");
	Sleep(1000);
    print(arr, 8);
	Sleep(1000);

    // 第二步,输入名字的长度并将牌向后移动相应的位数
    printf("\n第二步,你需要输入你名字的长度:\n");
    int name_len = 0;
    scanf("%d", &name_len);
	Sleep(1000);
    printf("\n接下来将会根据名字长度向后放几张牌,结果如下所示:\n");
	Sleep(1000);
    for (int x = 0; x < name_len; x++)
    {
        number_back(arr, 8, 1);
        print(arr, 8);
        Sleep(1000);
    }

    // 第三步,将最前面三张牌放到剩下牌的中间
    printf("\n第三步,将最前面三张牌放在剩下牌的中间,请输入目标位置\n");
	printf("(注意:目标位置在剩下的牌中第几张后面就输入几):\n");
    int the = 0;
    scanf("%d", &the);
    func_back(arr, 3, 0, the + 3);
	Sleep(1000);
    printf("\n结果如下所示:\n");
	Sleep(1000);
    print(arr, 8);
    Sleep(1000);

    // 将第一张牌放到最后
    printf("\n此时,第一张牌“%d”就是最终要拼接的牌,把它拿出来单独放好。\n\n", *arr);
    Sleep(1000);
    number_back(arr, 8, 1);
    printf("现在你手中的牌是:\n");
	Sleep(1000);
    print(arr, 7);
	Sleep(1000);

    // 第四步,根据选择的人对应数字,将最前面的牌放到剩余牌的中间
    int the_2 = 0;
    printf("\n第四步,如果你是南方人请输入1,北方人则输入2,不确定自己在南方还是北方就输入3:\n");
    scanf("%d", &the_2);
	Sleep(1000);
    printf("\n根据魔术要求和你选的哪方人对应的数字,将会拿出相应张数的最前面的牌,并放到剩余的牌中间,请输入目标位置\n");
	printf("(注意:目标位置在所有牌的第几张后面就输入几):\n");
    int the_3 = 0;
    scanf("%d", &the_3);
    func_back(arr, the_2, 0, the_3);
	Sleep(1000);
	printf("\n现在你的牌变为:\n");
	Sleep(1000);
    print(arr, 7);
	Sleep(1000);

    // 第五步,根据性别扔掉最前面的牌
    printf("\n第五步,如果你是男生请输入1,女生请输入2,将会根据所选数字扔掉最前面的牌:\n");
    int the_4;
    scanf("%d", &the_4);
    number_back(arr, 8, the_4);
	Sleep(1000);
    printf("\n丢掉之后的牌为:\n");
	Sleep(1000);
    print(arr, 7 - the_4);

    // 第六步,将最上面的牌放到最下面并重复操作7次
    printf("\n第六步,将最上面的牌放在最下面,并且重复操作7次(因为“见证奇迹的时刻”共有7个字哈哈哈!),如下所示:\n");
    for (int x = 0; x < 7; x++) 
	{
        number_back(arr, 7 - the_4, 1);
        printf("第%d次操作完成后牌为:", x + 1);
        print(arr, 7 - the_4);
        Sleep(1000);
    }
    int len = 7 - the_4;
    Sleep(1000);

    // 第七步,将最前面的牌留下或丢掉
    printf("\n最后一步,好运留下来→第一张牌放到最下面;烦恼丢出去→第一张牌丢掉。\n");
    while (len != 1)
    {
        number_back(arr, len, 1);
        printf("好运留下来→");
        print(arr, len);
        Sleep(1000);
        number_back(arr, len, 1);
        len--;
        printf("烦恼丢出去→");
        print(arr, len);
        Sleep(1000);
    }
	Sleep(1000);

    // 输出最终留下的那张牌
    printf("\n现在你的手中剩下的最后一张牌是:%d\n", *arr);
	printf("与之前单独拿出来保存的那半张牌刚好是对应的!\n\n");
	Sleep(1000);
	printf("———————★★★★★★★★恭喜你!成功复现了2024春晚刘谦魔术《守岁共此时》★★★★★★★★———————\n\n");

    return 0; // 程序结束
}

五、程序运行结果

利用C语言编程从数学角度揭秘2024春晚刘谦魔术《守岁共此时》_第5张图片

利用C语言编程从数学角度揭秘2024春晚刘谦魔术《守岁共此时》_第6张图片

你可能感兴趣的:(2024春晚,刘谦,魔术,数学原理,揭秘)