【例题描述】
由M只猴子围成一圈,从1到M进行编号,打算从中选出一个大王,经过协商,决定选出大王的规则:从第一个开始循环报数,
数到N的猴子出圈,下一个猴子从1开始报数。
【输入样例】
3 2
【输出样例】
3
方法一:
//模拟法
#include<iostream>
using namespace std;
#define MAX 100
long a[MAX+1];
long m,n,i,Count,k;
int main()
{
cin>>m>>n;
for(i=1;i<=m;i++)
a[i]=1;
k=m; //报数前,指向该数的前一只猴子
for(i=1;i<=m;i++)//出圈M只猴子
{
Count=0; //报数前,个数为0
while(Count<n)//当前报数<n,继续报数
{
if(k==m)//从最后一个位置移动到第一个位置
k==1;
else
k++;
if(a[k]==1)//当前猴子在圈内,报数
Count++;
}
a[k]=0;//猴子K出圈
}
cout<<k<<endl;
return 0;
}
分析:上程序发现:当猴子出圈后,程序也会一一检查是否出圈,在这里花了大量的时间。
方法二:利用数组存放下一个该报数的编号,以8只猴子为例,a[1]的值为2,意思是指2号猴子,
如下所示
a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] | a[8] |
---|---|---|---|---|---|---|---|
2 | 3 | 4 | 5 | 6 | 7 | 8 | 1 |
这样构成了一个环,如果5号猴子出圈就用语句a[4]=a[a[4]],以后报到4号猴子时,就直接指向6号猴子。
#include<iostream>
using namespace std;
#define N 100000
long a[MAX+1]
long m,n,i,Count,k;
int main()
{
cin>>m>>n;
for(i=1;i<=m-1;i++)
a[i]=i+1;
a[m]=1;//最后一只猴子指向第一只
k=m;//报数前,指向该报数的前一只猴子
for(i=1;i<=m;i++)//出圈M只猴子
{
for(Count=1;Count<=n-1;Count++)//只需报数n-1次,a[k]指向该出圈瘊子
k=a[k];
a[k]=a[a[k]];
}
cout<<k<<endl;//最后一只出圈的是大王
}
为了更好的理解我列出来第一次小循环后的数组值:
a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] | a[8] |
---|---|---|---|---|---|---|---|
3 | 3 | 4 | 5 | 6 | 7 | 8 | 1 |
所以每次点名到1的时候回跳过二号值,外循环进行到M次时要出去的k值即为大王的位置。
方法三:
倒退算法:如果我们知道这个子问题的答案:设X是猴王,那么根据对应关系就有以下的对应关系,F[i]=(F[i-1]+k)%i;(k为每次需要喊的次数)
//倒退算法
#include<iostream>
using namespacestd;
int main()
{
int i,m,k,ans;
cin>>m>>k;
ans=0;
for(i=2;i<m=;i++)
ans=(ans+k)%i;
cout<<k+1<<endl;//因为这里的位次是从0开始的k%k=0.
}