C++编程实现2024春晚刘谦魔术约瑟夫环问题

神奇的科技改变生活 · 未来的世界你想不到

C++编程实现2024春晚刘谦魔术约瑟夫环问题

从2009年到2019年,刘谦10年间5次亮相央视春晚舞台,一句“见证奇迹的时刻”成为刘谦的招牌台词。但从2019年在春晚表演《魔壶》之后,刘谦好像销声匿迹了,连续5年都与春晚无缘。2024年2月9日晚,刘谦终于带着最新魔术节目《守岁共此时》再次亮相春晚,神乎其技的表现让观众直呼不可思议。表演结束后,在微博和小红书等社交平台上,意犹未尽的网友们积极参与解密,力图还原刘谦将碎掉的牌重新组合成一张牌的全过程。

图片

网友们纷纷留言表示惊讶,“刘谦的魔术让昏昏欲睡的我支棱起来,两张碎牌拼到一起的时候,我鸡皮疙瘩都起来了”“所有观众都是刘谦的托,他与全国观众一起互动,让大家亲眼见证奇迹的发生”“这次魔术的互动感特别强,有一种奇迹在手中绽放的感觉”……

    刘谦春晚魔术是什么原理?学过编程的同学很快就想到是约瑟夫环。很多朋友从数学的角度做了分析和题解。其实通过编程来实现更容易。

图片


    首先我们有四张扑克牌,我们把它们标记为A,B,C,D。然后我们将这些扑克牌对折,撕开,并叠在一起。

1.起始状态:

A1 B1 C1 D1 A2 B2 C2 D2

当然,魔术中的一个步骤(按照我们的名字汉字个数将牌重新排序),这并不影响。比如我的名字是三个字,结果为:

D1 A2 B2 C2 D2 A1 B1 C1

我们将此作为起始状态吧!

第二步是将最前面三张插到中间任何一个位置。比如我这样做。

2.第二次操作后:

C2 D2 D1 A2 B2 A1 B1 C1

我们可以看到第一张牌和最后一张牌是相同的,这是魔术的关键

我们把第一张牌C2取出来。

D2 D1 A2 B2 A1 B1 C1

第三步按照南北方或者自定义方式将前面的牌插到中间。比如我是北方人,我这样做。

3.第三次操作后:

A2 B2 A1 B1 D2 D1 C1

我们可以看到最后一张仍然是C。

第四步按照性别将前面一张或两张扔掉。

4.第四次操作后:

B2 A1 B1 D2 D1 C1

第五步按照“见证奇迹的时刻”七个字放牌,每念一遍放一张牌到最后面,七次过后。

5.第五次操作后:

A1 B1 D2 D1 C1 B2

这次我们的C牌到了倒数第二个位置

6.第六次操作:

好运留下来:

B1 D2 D1 C1 B2 A1

烦恼丢出去:

D2 D1 C1 B2 A1

好运留下来,烦恼丢出去:

C1 B2 A1 D2

好运留下来,烦恼丢出去:

A1 D2 C1

好运留下来,烦恼丢出去:

C1 A1

好运留下来,烦恼丢出去:

C1

此时我们就剩最后一张牌,C1牌,就是取出来的C2牌的另一半。

这是巧合吗?不是的,这在数学上称为约瑟夫环问题,接下来我们用编程的思想去思考一下约瑟夫环问题,并使用c++语言编程来实现。

图片

约瑟夫环问题是一个古老而经典的数学问题,它描述了一个有 n 个人的圆圈,每个人都有一个编号,从某个位置开始报数,报到第 m 个人的时候,该人出列,然后重新开始报数。

让我们通过一个简单的例子来讲解约瑟夫环问题的解决过程:

假设现在有6个人,我们给他们编号1-6,报数间隔为2,从1号开始。我们用X来表示此人已经出列。

1.起始状态

1 2 3 4 5 6  

2.第一轮报数后,6移除后下一个为1,所以移除3

1 X 3 X 5 X  

3.第二轮报数后,3移除后从5开始,所以移除1:

1 X X X 5 X 

3.第三轮报数后:

X X X X 5 X 

我们可以看到,剩下的是编号为5的人。

用代码表示这个过程:

#include <iostream>using namespace std;#include<vector>void circle(int n, int m) {    // 创建一个大小为 n 的数组,表示约瑟夫环    vector<int> circle(n);    // 初始化数组,表示每个人的编号    for (int i = 0; i < n; ++i) {        circle[i] = i + 1;    }    int index = 0;  // 当前索引位置    // 循环直到只剩一个人    while (n > 1) {        // 找到下一个被移除的位置        index = (index + m - 1) % n;        // 输出当前被移除的人        cout << "位置" << circle[index] << "出列" << endl;        // 将当前位置的人移除,并将后面的人前移        for (int i = index; i < n - 1; ++i) {            circle[i] = circle[i + 1];        }        // 减少人的数量        --n;    }    // 输出最后剩下的人的编号    cout << "最后一个幸存者的位置为:" << circle[0] << endl;}int main() {    int n = 6;  // 人的总数    int m = 2;  // 报数的间隔    circle(n, m);    system("pause");    return 0;}

结果如下:

位置2出列位置4出列位置6出列位置3出列位置1出列最后一个幸存者的位置为:5

与魔术结合起来看,这个5号代表的就是C1牌,经过循环筛选后,留下来的就是我们需要的那张牌。


我们用c++代码来解决这个问题,用来求当给出总个数和间隔值时,经过循环筛选最后留下的值是哪个位置:

#include <iostream>using namespace std;#include <list>int circle(int n, int m) {    list<int> people;    // 初始化队列    for (int i = 1; i <= n; ++i) {        people.push_back(i);    }    auto it = people.begin();    // 循环直到队列中只剩一个人    while (people.size() > 1) {        // 移动到第 m 个人        for (int i = 1; i < m; ++i) {            ++it;            if (it == people.end()) {                it = people.begin(); // 循环到队列开头            }        }        // 移除当前位置的人,并重新初始化迭代器        it = people.erase(it);        if (it == people.end()) {            it = people.begin(); // 循环到队列开头        }    }    // 返回最后剩下的人的位置    return *people.begin();}int main() {    int n, m;    cout << "输入总人数为: ";    cin >> n;    cout << "输入报数间隔为: ";    cin >> m;    int survivor = circle (n, m);    cout << "最后一个幸存者的位置为: " << survivor << endl;    system("pause");    return 0;}

手动输入后结果为:

输入总人数为: 6输入报数间隔为: 2最后一个幸存者的位置为: 5

我们根据这个程序可以得出结果,报数间隔为2,每当个数为2的n次方时,幸存者的位置总是1。是不是很神奇?

返回动态列表