约瑟夫环问题

约瑟夫环:
它是一个数学的应用问题:已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。
算法原理:
约瑟夫环运作如下:
1、一羣人围在一起坐成 [2] 环状(如:N)
2、从某个编号开始报数(如:K)
3、数到某个数(如:M)的时候,此人出列,下一个人重新报数
4、一直循环,直到所有人出列 [3] ,约瑟夫环结束
应用场景:
猴子选大王:
由M(M <= 100)只猴子围成一圈,从1到M进行编号,打算从中选出一个大王。经过协商,决定出选大王的规则:从第一个开始循环报数,数到N(N <= 100)的猴子出圈,最后剩下来的就是大王。问哪一个编号的猴子成为大王。
通常解决这类问题时我们把编号从0~n-1,最后 [1] 结果+1即为原问题的解。
首先,我们把这n个猴子的序号编号从0~n – 1(当m大于等于n时,第一个出列的猴子编号是m % n,但是m % n 可能等于0,如果从1开始,出现0这个情况,就会漏掉。这样编号有助于简化出列过程),当数到m – 1的那个猴子出列:
分析:
第一次出列:
所有猴子编号:0, 1, 2, 3, 4,… n – 2, n – 1
第一次出列的猴子编号为 (m – 1)% n1, 之后它的下一个猴子又从0开始报数。
假设k1 = m % n1,(n1为当前猴子总数),第一个猴子出列之后,k1则是下一次新的编号序列的首位元素。因此,可以得到新的序列:
k1, k1+1, k1+2, k1+3, …, n – 2, n – 1, 0, 1, 2, …, k1-3, k1-2 (k1 – 1 第一次已经出列)。我们知道,第一个猴子还是从0开始报数,可得:
0, 1, 2, 3,…, n – 2
注意:
第二次每个猴子报的相应数字与第一次时自己相应的编号对应起来。
0 ——>k1
1 ——>k1 + 1
2 ——>k1 + 2

n – 2 ——> (k1 + (n – 2)) % n1
(n1为当前序列的总猴子数,因为是循环的序列,k1+n-1可能大于总猴子数)。
现在我们要解决的就是n – 1阶的约瑟夫问题。
后面的过程与前两次的过程一模一样,那么递归处理下去,直到最后只剩下一个猴子的时候,便可以直接得出结果
当我们得到一个猴子的时候(即一阶约瑟夫环问题)的结果。
假如得到了这个n-1阶约瑟夫环问题的结果为x(即最后一个出列的猴子编号为x),那么我们通过上述分析过程,可以知道,n阶约瑟夫环的结果
(x + k) % n (n为当前序列的总猴子数),而k = m%n。
那么有:
(x + m % n) % n,那么我们还可以将该式进行一下简单的化简:

当m < n时,易得上式可化简为:(x + m)% n

而当m >= n时,那么上式则化简为:(x % n + m % n % n)% n
即为:(x % n + m % n)% n
而 (x + m)% n = (x % n + m % n)% n
因此得证
(x+ m % n) % n = (x + m)% n
这样的话,我们就得到了递推公式,由于编号是从0开始的,那么我们可以令
f[1] = 0; //当一个猴子的时候,出队猴子编号为0
f[n] = (f[n-1] + m)%n //m表示每次数到该数的猴子出列,n表示当前序列的总猴子数。
代码如下:

import java.util.Scanner;
public class Main {
	public static void main(String[] args) {
		int n, m, i, x = 0;
		Scanner sca = new Scanner(System.in);
		n = sca.nextInt();
		m = sca.nextInt();
		for (i = 2; i <= n; i++)
		{
			x = (x + m) % i;
		}
		System.out.println(x + 1);
	}
}

测试数据:
输入 —— 输出
14 3 ——> 2
11 3 ——> 7
2 3 ———> 2
2 2 ———> 1
3 3 ———> 2

点赞