一、题目要求
一群猴子要选新猴王。新猴王的选择方法是:让N只候选猴子围成一圈,从某位置起顺序编号为1~N号。从第1号开始报数,每轮从1报到3,凡报到3的猴子即退出圈子,接着又从紧邻的下一只猴子开始同样的报数。如此不断循环,最后剩下的一只猴子就选为猴王。请问是原来第几号猴子当选猴王?
输入格式:
输入在一行中给一个正整数N(≤1000)。
输出格式:
在一行中输出当选猴王的编号。
输入样例:
11
输出样例:
7
二、代码
版本一、用cnt记录往后数的顺序,用数组Quit[i]记录退出与否与顺序。
好久未写,逻辑差点没理清。本题要点如下:
1)数组循环:i = i%N
2)何时序数+1,何时退出,需要准确判断条件并予以记录。
quit[i]==0 --> +1
quit[i]==0 && cnt%3 ==0 --> 退出
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
int N, i, cnt=0, monkey=0;
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
int [] quit = new int[N];
for(i=0;i<N;i++) {
quit[i] = 0;
}
for(i=0;monkey!=N-1;i++, i=i%N ) {
if(quit[i]==0) { // 计数。
cnt++; // 若该猴子未退出,序数+1
}
if(quit[i]==0&&cnt%3==0) { // 猴子**未退出**且**序数为3**,则退出,并记录是第几个退出的
monkey++; // 退出数记录
quit[i] = monkey;
}
}
for(i=0;i<N;i++) {
if(quit[i]==0) {
System.out.print(i+1);
}
}
}
}
版本二:公式推导法
这道题本质上是约瑟夫环问题,如何有效推导出来公式是最核心也是最难的地方。
问题:
简单定义下约瑟夫环问题,有n个人,序号1,2,3,…,N-1,N。现在开始从1开始数数,数到M的那个人被杀死。
后面的人从1重新开始数,再数到M的人被杀死。以此往复,直至剩下最后一个人为胜利者。
推导
递推公式: f ( N , M ) = f ( f ( N − 1 ) , M ) + M ) % N f(N, M) =f(f(N-1),M)+M)\%N\ f(N,M)=f(f(N−1),M)+M)%N
1)
f
(
N
,
M
)
f(N, M)
f(N,M):N个人报数,报M的人退出,最后胜利者编号;
2)
f
(
N
−
1
,
M
)
f(N-1, M)
f(N−1,M):N-1个人报数,报M的人退出,最后胜利者编号。
现在用题目N=11,来分析一下退出过程(图来自参考2):
绿色:序号
黄色:退出序号
红色:胜利者编号。
验证
用上图从下—>上来分析一下,7可以看作不同人数时的最终胜利者,他的位置就是胜利者下标:
1)当N=1,7在下标0的位置,
f
(
1
,
3
)
f(1, 3)
f(1,3)=0;
2)当N=2,7在下标1的位置,
f
(
2
,
3
)
f(2, 3)
f(2,3)=1;
3)当N=3,7在下标1的位置,
f
(
3
,
3
)
f(3, 3)
f(3,3)=1;
4)当N=4,7在下标0的位置,
f
(
4
,
3
)
f(4, 3)
f(4,3)=0;
5)当N=5,7在下标3的位置,
f
(
5
,
3
)
f(5, 3)
f(5,3)=3;
6)当N=6,7在下标0的位置,
f
(
6
,
3
)
f(6, 3)
f(6,3)=0;
7)当N=7,7在下标3的位置,
f
(
7
,
3
)
f(7, 3)
f(7,3)=3;
8)当N=8,7在下标6的位置,
f
(
8
,
3
)
f(8, 3)
f(8,3)=6;
9)当N=9,7在下标0的位置,
f
(
9
,
3
)
f(9, 3)
f(9,3)=0;
10)当N=10,7在下标3的位置,
f
(
10
,
3
)
f(10, 3)
f(10,3)=3;
10)当N=11,7在下标6的位置,
f
(
11
,
3
)
f(11, 3)
f(11,3)=6;
验证一下:
f
(
1
,
3
)
f(1, 3)
f(1,3) = 0;
f
(
2
,
3
)
f(2, 3)
f(2,3) =(
f
(
1
,
3
)
f(1, 3)
f(1,3)+3)%2=1;
f
(
3
,
3
)
f(3, 3)
f(3,3) =(
f
(
2
,
3
)
f(2, 3)
f(2,3)+3)%3=1;
f
(
4
,
3
)
f(4, 3)
f(4,3) =(
f
(
3
,
3
)
f(3, 3)
f(3,3)+3)%4=0;
f
(
5
,
3
)
f(5, 3)
f(5,3) =(
f
(
4
,
3
)
f(4, 3)
f(4,3)+3)%5=3;
f
(
6
,
3
)
f(6, 3)
f(6,3) =(
f
(
5
,
3
)
f(5, 3)
f(5,3)+3)%6=0;
f
(
7
,
3
)
f(7, 3)
f(7,3) =(
f
(
6
,
3
)
f(6, 3)
f(6,3)+3)%7=3;
f
(
8
,
3
)
f(8, 3)
f(8,3) =(
f
(
7
,
3
)
f(7, 3)
f(7,3)+3)%8=6;
f
(
9
,
3
)
f(9, 3)
f(9,3) =(
f
(
8
,
3
)
f(8, 3)
f(8,3)+3)%9=0;
f
(
10
,
3
)
f(10, 3)
f(10,3) =(
f
(
9
,
3
)
f(9, 3)
f(9,3)+3)%10=3;
f
(
11
,
3
)
f(11, 3)
f(11,3) =(
f
(
10
,
3
)
f(10, 3)
f(10,3)+3)%11=6;
可以看到全部正确。
分析
用上图再从上—>下分析一下:
<1>对N个人编号:0,1,2,3,… ,M-2,M-1,M, … ,N-2, N-1;
<2>第一轮退出: 0,1,2,3,…, M-2, M,…,N-2,N-1.
<3>重新排列: M,…,N-1,N,0,1,2,3,…,M-2,(*)
<4>重新编号: 0,1, 2, 3, …, N-3, N-2,(**);
所以(<4>+M)%N == <3> ,即可得只要得出N-1次序列<4>,即可得出第N次退出的位置:(<4>+M)%N
本次的例子可具体分析如下:
黄色:退出
绿色:补上下一步退出的
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
int quit=0, i=0, N=0;
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
for(i=2;i<=N;i++) {
quit = (quit+3)%i;
}
System.out.println(quit+1);
}
}
三、参考
1、通用约瑟夫环解法:PAT-JAVA-5-28 猴子选大王 (20分)
#include<iostream>
using namespace std;
int main()
{
int N;//人的总个数
int M;//间隔多少个人
cin>>N;
cin>>M;
int result=0;//N=1情况
for (int i=2; i<=N; i++)
{
result=(result+M)%i;
}
cout<<"最后自杀的人是:"<<result+1<<endl;//result要加1
return 0;
}
2、(五星推荐,清晰简洁)约瑟夫环原理讲解:约瑟夫环——公式法(递推公式)
3、约瑟夫环细节讲解:约瑟夫问题实现的方法总结