描述
约瑟夫问题:有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),从第1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1开始报数。就这样,直到圈内只剩下一只猴子时,这个猴子就是猴王,编程求输入n,m后,输出最后猴王的编号。
输入
每行是用空格分开的两个整数,第一个是 n, 第二个是 m ( 0 < m,n <=300)。最后一行是:
0 0
输出
对于每行输入数据(最后一行除外),输出数据也是一行,即最后猴王的编号
样例输入
6 2
12 4
8 3
0 0
样例输出
5
1
7
问题分析:
看到这个问题一打眼肯定是选择模拟整个过程来解题,但细心分析可以发现,这既然是一个数学问题,创立之初肯定不会是用计算机模拟的,肯定是有数学公式可以遵循的。所以我们可以从递推的角度来思考这个问题。
设n个人围城一圈报到m出列,最后的胜利者的编号设为f(n,m)。
首先我们先来看一个表格:
n | m | f(n,m) |
---|---|---|
1 | 2 | 1 |
2 | 2 | 1 |
3 | 2 | 3 |
4 | 2 | 1 |
5 | 2 | 3 |
6 | 2 | 5 |
这个表格呢,可以这么理解
n=6,m=2.最后胜利者为五号。
那我们来尝试推一下n=5,m=2时胜利者编号。当n=5时相当于n=6问题执行完一轮之后的子问题。n=6执行完一轮之后出局者是二号,那么就从三号开始报数,这时问题就变成了,五个人围成一圈报数,数到2的人退出,编号1为1、3、4、5、6这五个人(3号报数字1)。
编号 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
n=1 | 1 | 2 | 3 | 4 | 5 | 6 |
n=2 | 3 | 4 | 5 | 6 | 1 | |
n=3 | 5 | 6 | 1 | 3 | ||
n=4 | 1 | 3 | 5 | |||
n=5 | 5 | 1 | ||||
n=6 | 5 |
我标记黄色的数据是每一轮出局的人。n为当前为第n轮。
那么我们来思考接下啦一个问题。已知六个人围城一圈报到2出局,最后胜利者为5号,那么五个人围城一圈报到2出局,那么最后胜利者为几号?
解:这个问题不难理解,这就相当于六个人的胜利者少执行一轮报数,可知胜利者为5-2=3;三号就是胜利者。
那么我们仔细思考一下就可以把m看作周期T,每少执行一轮就将胜利者编号减去周期T,相反,每多执行一轮就将胜利者编号加上周期T。但是很明显这最后会出现不属于范围内的编号。所以我们就要对每一轮的结果f(n,m)取余。
取余的过程可以这么理解:f(n,m)-f(n,m)/n;当前编号包括多少个n,可知最后结果为每包括一个n编号就从1开始重置,所以取余结果为零的话编号就为1号,所以最后结果要加上一个1。
代码:
#include<iostream>
#include<cstdio>
using namespace std;
int f(int ,int);
int main()
{
int n,m;
cin>>n>>m;
while(n!=0&&m!=0)
{
cout<<f(n,m)+1<<endl;
cin>>n>>m;
}
return 0;
}
int f(int n,int m)
{
if(n==1) return 0;
else return (f(n-1,m)+m)%n;
}
来源:CSDN
作者:amazingee
链接:https://blog.csdn.net/amazingee/article/details/104061843