约瑟夫环问题学习小记

问题的提出

有n个人围成一个圈,按顺时针编号为1到n。现在从编号为1的人开始报1,下一个人报2,如此类推,报到m的人就退出,接着下一个人继续从1开始报。问最后一个剩下的人是谁。

解法1

可以通过链表的形式来模拟这个过程,时间复杂度为 O ( n m ) O(nm) O(nm)

解法2

我们把每个人的编号都-1,也就是编号变为了从0到n-1,显然这两个问题是等价的。
第一个出去的人的编号肯定是 ( m − 1 )   m o d   n (m-1)\bmod n (m1)modn,然后接着从 k = m   m o d   n k=m\bmod n k=mmodn开始报。
那么剩下的人就是 k , k + 1 , . . . , n − 1 , 0 , . . . , k − 3 , k − 2 k,k+1,...,n-1,0,...,k-3,k-2 k,k+1,...,n1,0,...,k3,k2
考虑对这些人的编号做一下变换:
k 变 为 0 k变为0 k0
k + 1 变 为 1 k+1变为1 k+11

n − 1 变 为 n − k − 1 n-1变为n-k-1 n1nk1
0 变 为 n − k 0变为n-k 0nk

k − 2 变 为 n − 2 k-2变为n-2 k2n2
k − 1 变 为 n − 1 k-1变为n-1 k1n1
不难发现其实就是把 i i i变为 i − k i-k ik
那么当我们把第 k − 1 k-1 k1个人去掉后,剩下的游戏就等价于一个大小为 n − 1 n-1 n1的约瑟夫环。设其最后答案为 x ′ x' x,大小为n的约瑟夫环的答案为 x x x,不难发现有 x = ( x ′ + m ) x=(x'+m)%n x=(x+m)
通过这种思路不断地递归下去不难发现有如下递推式:
f [ n ] f[n] f[n]表示大小为n的约瑟夫环每次走 m m m步的最终答案。
f [ 1 ] = 0 f[1]=0 f[1]=0
f [ n ] = ( f [ n − 1 ] + m )   m o d   n f[n]=(f[n-1]+m)\bmod n f[n]=(f[n1]+m)modn
至此我们得到了一个比算法1要优秀的 O ( n ) O(n) O(n)解法。

小拓展

当n很大而m很小的时候,比如说 n &lt; = 1 0 9 , m &lt; = 1 0 6 n&lt;=10^9,m&lt;=10^6 n<=109,m<=106,这时就有一些比较特殊的解法。
当模数逐渐变大的时候,我们发现可能一次可以跳很多步且在这期间实际并不用取模,这时就可以一次性把这些位置都处理掉,从而大大的节约时间。
设当前模数为i,每一步跳k个位置,我们一次性跳t步。
不难得到 w + t ∗ k &lt; i + t − 1 w+t*k&lt;i+t-1 w+tk<i+t1化简后有 t &lt; i − w − 1 k − 1 t&lt;\frac{i-w-1}{k-1} t<k1iw1
那就是说这t步我们是可以一次性处理掉的。

代码
int solve(int n,int k)
{
	int i=2,w=0;
	while (i<=n)
	{
		int t=(i-w-1)/(k-1);
		if ((i-w-1)%(k-1)==0) t--;
		if (i+t>n) {w+=(n-i+1)*k;break;}
		w+=t*k;i+=t;
		w=(w+k)%i;i++;
	}
	return w;
}

在维基百科上面还有一种更优秀的解法:
这里写图片描述
至于如何证明,这个坑还是留着以后再填吧。据说 具体数学 里面有关于这条式子的证明。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
约瑟夫问题是一个经典的问题,我们不妨将这个经典问题进行扩展,变成一个双向的约瑟夫问题。   已知n个人(不妨分别以编号1,2,3,…,n 代表 )围坐在一张圆桌周围,首先从编号为 k 的人从1开始顺时针报数,1, 2, 3, ...,记下顺时针数到 m 的那个人,同时从编号为 k 的人开始逆时针报数,1, 2, 3, ...,数到 m 后,两个人同时出列。然后从出列的下一个人又从 1 开始继续进行双向报数,数到m的那两个人同时出列,…;。依此重复下去,直到圆桌周围的人全部出列。直到圆桌周围只剩一个人为止。   如果双向报数报到 m 时落在同一个人身上,那本次出列的只有一个人。   例如:5,1,2。则总共5个人,从1开始顺时针报数,数到2,定位编号2;同时从1开始报数数到2,定位编号5;2和5同时出列。然后继续开始报数,顺时针报数3,4,定位到4;逆时针报数4,3,定位3;4和3同时出列。最后剩余的为编号1。输出为:2-5,4-3,1,。   如果输入:6,2,3。则输出:4-6,2,1-3,5,。其中第2次只输出一个2,表示第二次双向报数时,恰好都落在编号2上,所以只有一个编号出列。 输入 n,k,m 输出 按照出列的顺序依次输出编号。同时出列编号中间用减号“-”连接。 非法输入的对应输出如下 a) 输入:n、k、m任一个为0 输出:n,m,k must bigger than 0. b) 输入:k>n 输出:k should not bigger than n.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值