这里讲其中一种:
问题描述:
一堆猴子都有编号,编号是1,2,3 …n ,这群猴子(n个)按照1-m的顺序围坐一圈,从第1开始数,每数到第m个,该猴子就要离开此圈,这样依次下来,直到圈中只剩下最后一只猴子,则该猴子为大王。
输入:
n,m (m, n<10000)
输出:
大王的编号
模拟实现的代码:
#include <iostream>
#include <cstdio>
using namespace std;
const int N=1e5+100;//猴子个数的范围,可调节
int a[N];//a数组用来存储猴子之间的关系,这题只需要用到后继,然后将尾部与首部相连
void build(int st,int ed)//a[i]的值表示在位置i的猴子的后一个猴子的位置是a[i]
{
a[ed]=st;//将最后一个猴子指向第一个猴子的位置
for(int i=st;i<ed;i++)
a[i]=i+1;//除最后一个猴子,其他猴子都指向下一个猴子
}
int solve_monkey(int m,int n)//对于每组m,n,输出猴子大王的编号
{
if(n==1) return m;//当n=1时, 最后的猴子一定是最后一个猴子m
build(1, m);//建立m个猴子的直接后继
int pos=1;//表示最先从编号为1的猴子开始数,pos表示当前数到的猴子的序号
int cnt=1;//表示当前数到第几个猴子,因为第一个猴子已数过,故cnt=1
while(a[pos]!=pos)//a[pos]!=pos 说明下一个猴子不是自己本身,即猴子数>=2个,需继续数猴子
{
cnt++;//数的猴子加1,即将序号为a[pos]的猴子已经数过
if(cnt==n) //当cnt=n时,说明该猴子要出圈
{
a[pos]=a[a[pos]];//将pos位的猴子的直接后继(a[pos]) 改为 a[pos]位猴子的直接后继(a[a[pos]])
pos=a[pos];//继续向后数猴子,当前数到的猴子是序号为更新后的a[pos]的猴子,cnt计数为1
cnt=1;
}
else
pos=a[pos];//如何不是第n个猴子,继续往后数,将pos向后移动
}
//跳出while循环,说明此时猴子个数为1,该猴子的序号为pos,return pos
return pos;
}
int main()
{
int c,d;
while(scanf("%d%d",&c, &d)==2)//c对应题目中的m,d对应题目中的n
{
if(c<=0||d<=0)
{
printf("输入数据不合法\n");
continue;
}
printf("%d个猴子,数到%d 个,输出为大王的猴子是:%d号\n", c, d, solve_monkey(c,d));
}
return 0;
}
利用数学方法的代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int main()
{
int m,n;
while(~scanf("%d%d", &n, &m))
{
int r=0;
for(int k=1;k<=n;++k) r=(r+m)%k;
cout<< r+1 << endl;
}
return 0;
}
理解:(主要是倒推)
将总数为n依次编号为0,1,2,···,n-1,此轮去掉的编号是(m-1)mod n,现在规定下一轮最先开始数的设为编号0,故此轮编号为(m+i)mod n对应下一轮编号为i\ 。以n=5,m=3为例:
编号 | |||||
---|---|---|---|---|---|
当前轮 | 0 | 1 | 2 | 3 | 4 |
对应下一轮编号 | 2 | 3 | 已去 | 0 | 1 |
反过来,这轮(总数为n-1)的编号为i的对应上一轮(总数为n)编号为
(
m
+
i
)
m
o
d
n
(m+i) mod \ n
(m+i)mod n的。比如样例中的下一轮编号为3的,对应的上一轮的编号为(m+3)%n=(3+3)%5=1.
由此,这样就可以当总数为1时,那么这个猴子就是最后留下来的猴子(在总数为1时,该猴子的编号为0),只要依次向上一轮求该猴子的编号,就可求出这个猴子在总数为n时的编号,那么该编号就是最终结果。
int r=0;//即总数为1时,该猴子的编号为0
// k表示猴子的总数,当k为n时,即表示第一轮,n-1表示第二轮,···,1表示最后一轮
for k from 2 to n
r=(r+m)%k;
cout<<(r+1)<<endl;