今天和大家分享一下约瑟夫问题的结论和数学推导。
对于传统的约瑟夫问题(例如猴子选大王问题),一般的解法就是直接模拟的方法,即数组下标法和循环链表法。能以以上两种方法做出约瑟夫问题的人,编程的功底都十分深厚。但是在正式的如蓝桥杯、acm尤其是在模拟赛等比赛中,此类问题均是作为妥妥的水题出现。而两种模拟法均会消耗大量的时间,是不讨好的行为。
但是如果我们在比赛中可以做到直接使用约瑟夫问题的结论来解决问题,那么我们的解题效率将会大大提升。话不多说,我们直接上核心代码。
int main()
{
int n = 0;//n只猴子
int m = 0;//报数m
int s = 0;//每次出局的猴子编号
cin >> n >> m;//scanf("%d %d",&n,&m);
for (int i = 2; i <= n; i++)
{
s = (s + m) % i;
}
printf("%d\n", s + 1);
}
(!!!注意这里是从第一个猴子开始报数的代码,从第k个猴子开始报数的代码略有不同。)
不要惊讶。没错,就是这么简单。你可能不知道这是为什么,那么我们接下来看一下自然语言解释下的约瑟夫问题的结论吧。
自然语言解释
当有n只猴子的时候,他们的编号依次是0、1、2、3、4、………、n-1。假设最后编号为y的猴子会留下来。
那么将该问题放在数学语言下为:
已知n和m,求解约瑟夫问题的函数(映射),那么代入数据,我们目标解(因变量)y就可以解出来了。
那么该映射是什么样子的呢?
我们先来看第一轮报数。
因为数到m的那个猴子会出列(因为n可能小于m,所以上句话不是第m个猴子出列),那么此轮中编号为(m-1)%n的猴子会出列。
第二轮报数
编号为(m+0)%n的猴子将做为第二轮编号为0的人,此轮编号为(m+i)%n的人将做为下一轮编号为i的人,重复第一轮。
············
Y(1)= 0;
Y(2)= ( m + Y(1) ) % 2;
Y(3)= ( m + Y(2) ) % 3;
············
由数学归纳法得出第i轮(i<n)的映射:Y(i)=( m + Y(i-1) ) % i 。
那么我们就可以得到这个每轮出圈猴子对应的映射(函数关系式):Y(i)=( m + Y(i-1) ) % i 。
要知道Y(i),就必须知道Y(i-1)······而最后Y(1)=0;
因此,我们的整个核心代码就出来了,n-1重循环代表n-1轮,(也可以理解为n个猴子每次提一个,共踢了n-1个),循环体就是通过求模求解被踢的猴子。
故有
int main()
{
int n = 0;//n只猴子
int m = 0;//报数m
int s = 0;//每次出局的猴子编号
cin >> n >> m;//scanf("%d %d",&n,&m);
for (int i = 2; i <= n; i++)
{
s = (s + m) % i;
}
printf("%d\n", s + 1);
}
怎么样你明白了么?