0001~约瑟夫问题

1. 一个杀人游戏

这个问题是以弗拉维奥·约瑟夫命名的,它是1世纪的一名犹太历史学家。他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中。他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定谁杀掉谁。约瑟夫斯和另外一个人是最后两个留下的人。约瑟夫斯说服了那个人,他们将向罗马军队投降,不再自杀。约瑟夫斯把他的存活归因于运气或天意,他不知道是哪一个。[1]


2. 数学解法一

传统解法为循环链表模拟出队流程,详见[1]。这里讨论更高效的数学解法。

要求解的是最后剩下的人的编号。解决思路是如果将杀人后的环重新编号,可以让求解的问题仍与杀人前等价,但问题规模(人数n)却可以减小 1,这样只要找到杀人前后新旧编号的递推关系,就可以利用规模为 1 时最后剩下人的编号一定为 0 的特点,反推回更大问题规模时的编号。

囚犯个数为 n,每次杀掉第 m 个人,囚犯编号从 0 到 n - 1

从 0 开始编号是为了下面公式中求余情况简单,不然如从 1 开始编号,当对 n 求余为 0 时要特殊修正为 n,在求解出编号后可以通过加 1 恢复成常见的从 1 开始编号方式

如规模为 n 时某个人 X,对应编号记为 Xn,规模变为 n - 1 时,从被杀的那个人后重新编号,X 的新编号为  Xn-1

可以通过下面的例子看出 Xn-1 与 Xn 相差为被杀的那个人编号加 1,即 m % n, 但考虑 Xn-1 + m % n 后可能超过 n,因此最终关系为 Xn = (Xn-1 + m % n) % n,根据同余性质[2],等价为 Xn = (Xn-1 % n + m % n) % n,等价为 Xn = (Xn-1 + m) % n

对于 n = 5, m = 8

Xn 0 1 2 3 4
杀人后 0 1 被杀 3 4
Xn-1 2 3 0 1

如最后剩下的那个人为 Y,规模为 n 时的编号为 Yn,显然 Y1 = 0,利用 Xn = (Xn-1 + m) % n 即可递推求得 Yn,代码详见[1]

也可推广为求解剩下多个人的问题,比如上面提到的历史上 41 人剩 2 人,设两人为A、B,则 A2 = 0 和 B2 = 1 带入递推公式求得 A41、B41 的值

标红为死的人,每次死人后重新编号

第 1 次死人,规模 n 第 2 次死人,规模 n - 1 第 a 次死人 第 n 次死人,规模 1
0 1 ... m % n - 1 0 1 ... m % (n - 1) - 1 0 1 ... m % (n - a + 1) - 1

最终问题转换为已知小规模时的新编号和新旧编号关系,求解大规模时的旧编号问题。

3. 数学解法二[3]

前面的解法是从某个人的编号和编号变化考虑,这个解法则是从某个人报数时的时刻和时刻变化考虑,这里“时刻”指的是所有人报的数中的第几个,从 0 开始算(为了 b < m - 1下面化简方便

思路为 Y 报数的时刻为 n * m - 1,通过求得 Y 前后两次报数的时刻关系,即可反推回 Y 第一次报数的时刻,而这个时刻就是 Y 的编号

如 Y 在 p 时刻报了数,同时 p 时刻 Y 不会死,即 (p + 1) % m != 0,将前面已经死的人数设为 a,则有 p = a * m + b,0 <= b < m - 1。可以参考下表理解

标红为死人的时刻

第 1 次死人 第 2 次死人 第 a 次死人 第 a + 1 次死人 第 n 次死人
0 1 ... m - 1 m ... 2m - 1 ... am - 1 am ...p ... am + m - 1   n * m - 1

Y 在 p 时刻后的下一次报数时刻设为 q,q 为 p 在剩下的 n -a 个人报数后,即 q = p + n - a,将等式变为 p 用 q、n、m 表示则得到 p = q - n + (q - n - b) / (m - 1),由 b < m - 1,得到 p = q - n + floor((q - n) / (m - 1)),这样就可从后面的报数时刻求得前面的报数时刻

上面两种解法都可以推广为第 j 个被杀的人编号,和第 j 个人被杀后第 k 个报数的人的编号

相关资料

[1] 维基百科

https://zh.wikipedia.org/wiki/%E7%BA%A6%E7%91%9F%E5%A4%AB%E6%96%AF%E9%97%AE%E9%A2%98

[2] 同余性质

https://blog.csdn.net/xiaofengsheng/article/details/4812535

[3] 约瑟夫问题解法

http://maskray.me/blog/2013-08-27-josephus-problem-two-log-n-solutions

原文地址:https://www.cnblogs.com/dujianfeng/p/9256585.html

时间: 2024-10-07 20:19:12

0001~约瑟夫问题的相关文章

一个不简洁的约瑟夫环解法

约瑟夫环类似模型:已知有n个人,每次间隔k个人剔除一个,求最后一个剩余的. 此解法为变种,k最初为k-2,之后每次都加1. 例:n=5,k=3.从1开始,第一次间隔k-2=1,将3剔除,第二次间隔k-1=2,将1剔除.依此类推,直至剩余最后一个元素. 核心思路:将原列表复制多份横向展开,每次根据间隔获取被剔除的元素,同时将此元素存入一个剔除列表中.若被剔除元素不存在于剔除列表,则将其加入,若已存在,则顺势后移至从未加入剔除列表的元素,并将其加入.如此重复n-1次.面试遇到的题,当时只写了思路,没

【c语言】数据结构(约瑟夫生者死者游戏的问题)

约瑟夫生者死者游戏:30个旅客同乘一条船,因为严重超载,加上风高浪大,危险万分:因此船长告诉大家,只有将全船一半的旅客投入海中,其余人才能幸免遇难.无奈,大家只得同意这种办法,并议定30个人围成一圈,由第一个人开始,依次报数,数到第9个人,就把他投入大海中,然后从他的下一个人开始从1数起,数到第9个人,再将她投入大海,如此循环,直到剩下15个人乘客为止.问哪些位置是将被扔到大海的位置. 解法有许多种,可以用数组,应为涉及到删除操作,数组(顺序线性表)比较麻烦,但不必要删除,只需要给跳船的人(元素

算法系列:约瑟夫斯问题

约瑟夫斯问题(有时也称为约瑟夫斯置换),是一个出现在计算机科学和数学中的问题.在计算机编程的算法中,类似问题又称为约瑟夫环. 有{\displaystyle n}个囚犯站成一个圆圈,准备处决.首先从一个人开始,越过{\displaystyle k-2}个人(因为第一个人已经被越过),并杀掉第k个人.接着,再越过{\displaystyle k-1}个人,并杀掉第k个人.这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着. 问题是,给定了{\displaystyle n}和{

ytu 1067: 顺序排号(约瑟夫环)

1067: 顺序排号Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 31  Solved: 16[Submit][Status][Web Board] Description 有n人围成一圈,顺序排号.从第1个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来的第几号的那位. Input 初始人数n Output 最后一人的初始编号 Sample Input 3 Sample Output 2 HINT Source freepro

约瑟夫环 C语言 单循环链表

/*---------约瑟夫环---------*/ /*---------问题描述---------*/ /*编号为1,2,-,n的n个人围坐一圈,每人持一个密码(正整数). 一开始任选一个正整数作为报数上限值m, 从第一个人开始自1开始顺序报数,报到m时停止. 报m的人出列,将他的密码作为新的m值,从他的下一个人开始重新从1报数, 如此下去,直至所有人全部出列为止.试设计一个程序求出列顺序.*/ /*---------问题分析---------*/ /*n个人围坐一圈,且不断有人出列,即频繁

用ArrayList(解决约瑟夫问题)

约瑟夫问题(Josephus problem)又称为约瑟夫斯置换,是一个出现在计算机科学和数学中的问题.在计算机编程的算法中,约瑟夫问题类似问题又称为约瑟夫环."丢手绢问题". 据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身

约瑟夫问题的变种 LA3882

题目大意: N个数排成一圈,第一次删除m,以后每k个数删除一次,求最后一被删除的数. 如果这题用链表或者数组模拟整个过程的话,时间复杂度都将高达O(nk),而n<=10000,k<=10000 目测会直接TLE. 那么有没有其他的方法呢?答案是有的. 我们先忽略掉m, 分析一下每k个数删除一次,那就是经典的约瑟夫问题了. 那么,将每个数(1~n)按顺序编号为0~n-1 设第一个删除的数的编号为x,则x= k %n-1 (注意是编号,真正删除的数为编号+1) 那么剩下的n-1个数可以组成一个新的

【python小练】0001

第 0001 题:做为 Apple Store App 独立开发者,你要搞限时促销,为你的应用生成激活码(或者优惠券),使用 Python 如何生成 200 个激活码(或者优惠券)? # coding = utf-8 __author__= 'liez' import random def make_number(num, length): str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' a = []

And Then There Was One(约瑟夫问题变形)

题目链接:http://poj.org/problem?id=3517 And Then There Was One Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 5014   Accepted: 2685 Description Let’s play a stone removing game. Initially, n stones are arranged on a circle and numbered 1, …