概述
【中学】大家一起做游戏
幼儿园的小朋友们刚学习了如何数数,阿姨在下课时组织大家一起玩游戏。规则如下:所有的小朋友绕成一圈,顺序排号,从第一个小朋友开始报数,凡是报到固定数字(例如5)的,都退出该游戏,直到只剩下一位小朋友游戏才中止。
每个小朋友都希望自己能有更多的练习数数的机会,所以都希望成为最终被留下的那位。
现在,请大家帮小朋友们计算一下,在第一次排号的时候排到第几位才能成为最终被留下的小朋友。
输入:
小朋友的个数(<=50) 要被练习的数字
输出:
最终被留下的小朋友的序号
样例:
序号 | 测试输入 | 期待的输出 | 额外进程 |
---|---|---|---|
1 | 4 3↵ | The left child is NO 1.↵ | 0 |
思路
我们最直接想到的就是用一个数组模拟小朋友,则数组下标为小朋友序号,对应的值表示小朋友的死活,然后通过模拟得出最后剩下的小朋友
代码1
这段代码来自我的一位同学(找不到我自己最开始写的了,已授权)
#include<stdio.h>
int main(){
int a[99],b[99],i=0,j=0,m=0,n=0,x=0,y=0;
scanf("%d %d",&m,&n);
while(i<m){
a[i]=i+1,i++;
}
while(j<n){
b[j]=j+1,j++;
}
i=0,j=0;
while(x!=m-1)
{
while(i<m)
{
if(a[i]!=0)
{
a[i]=b[j];
if(a[i]==n)
{
a[i]=0,j=0,i++,x++;
}
else{
y=i,i++,j++;
}
}
else{
i=i+1;
}
}
i=0;
}
printf("The left child is NO %d.n",y+1);
return 0;
}
同学们可以试着阅读一下
这段代码里使用数组a模拟小朋友,数组b模拟要数的数字,整个逻辑是没问题的,但最后却失败了,因为超时
那我们就来看看是哪些部分影响了复杂度
首先,就是开头的两个循环
while(i<m){
a[i]=i+1,i++;
}
while(j<n){
b[j]=j+1,j++;
}
本意是想要初始化数组,但从整体考虑未免有些多余:
- 一是纵览整段代码,b的存在完全可以用一个计数器代替
- 二是数组a完全可以使用下标作为小朋友的序号,而不用依次赋值,并且所赋的值也没有在后续的代码中表现出应有的作用
- 三是,既然数组初始化值全为0,那不妨就将0作为小朋友的“活”,不一定要按照经验1“活”0“死”
其次,我们看到这段代码用了两层循环来模拟圆环,但实际上我们可以通过计数器加条件判断将这个循环减少到一层
将上述问题修改后,我们得到
代码2
#include <stdio.h>
main()
{
int a[51] = { 0 };
int boy, num, death = 0, i, num_ = 0;
scanf("%d%d", &boy, &num);
for (i = 1; death != boy; i++)
{
if (i > boy) i = 1;
if (a[i] == 0) num_++;
if (num_ == num)
{
num_ = 0;
a[i] = 1;
death++;
}
}
printf("The left child is NO %d.n", i - 1);
}
这次看起来就简洁很多了,测试也可以完美通过
课外思考
我们之前说过,对于这类问题可以先尝试优化数学模型,那道题是否也可以呢?
答案是肯定的,这道题其实就是“约瑟夫问题”,可以使用递归算法:
m 个小朋友,数字为 n ,先报一次数,报到 n 的小朋友(序号一定是 n%m )拉出去毙了 退出游戏,此时还剩下 m-1 个小朋友
这不就相当于 m-1 个小朋友,数字为 n,求最后剩下的序号吗,只不过我们要对序号作一些变换,原来序号是 n%m+1 的小朋友,现在的新序号是1,原来序号是 n%m-1 的小朋友现在序号是 m-1 ,也就是:
最初 | 离开后 | 新序号 |
---|---|---|
1 | 1 | m-n%m+1 |
2 | 2 | m-n%m+2 |
3 | 3 | m-n%m+3 |
······ | ······ | ······ |
n%m-1 | n%m-1 | m-1 |
n%m | - | - |
n%m+1 | n%m+1 | 1 |
······ | ······ | ······ |
m-2 | m-2 | m-n%m-2 |
m-1 | m-1 | m-n%m-1 |
m | m | m-n%m |
观察规律可以得到 x = ( x’ + n - 1 ) % m + 1
我们假设F(m)为m个小朋友时的答案,则 F(m) = ( F(m-1) + n - 1 ) % m + 1
详细过程可以参考 这或许是你能找到的最详细约瑟夫环数学推导! - 负雪明烛的文章 - 知乎
这篇文章也提供了C语言的解法,同学们可以看看和我的有什么不同
代码
#include <stdio.h>
int FF(int x, int y)
{
if (x == 1) return x;
else return (FF(x - 1, y) + y - 1) % x + 1;
}
int main()
{
int boy, num;
scanf("%d%d", &boy, &num);
printf("The left child is NO %d.n", FF(boy, num));
}
事实上,递归的次数是固定的,所以我们还可以写成
#include <stdio.h>
int main()
{
int boy, num, f = 1;
scanf("%d%d", &boy, &num);
for (int i = 2; i <= boy; i++)
{
f = (f + num - 1) % i + 1;
}
printf("The left child is NO %d.n", f);
}
最后
以上就是专注母鸡为你收集整理的【中学】大家一起做游戏 - 约瑟夫问题【中学】大家一起做游戏的全部内容,希望文章能够帮你解决【中学】大家一起做游戏 - 约瑟夫问题【中学】大家一起做游戏所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复