以后的搬砖转化一下思路,以一本书为主体,进行类似读书笔记式的讨论和总结,首先便从这本高德纳的《具体数学》开始吧。
大致翻了一下这本书,能够明显感觉到大师所著的书和国内的一些出版物是没办法比的。看了一下这本书的序言,发现这本书有一个很大的特色就是“数学涂鸦”,好像是有一部分读者在阅读该书时的注释被收录到了这本书当中,这些涂鸦中不仅仅有俏皮的玩笑,还不乏精巧的解题方略,与作者在论述清晰优雅而又诙谐的语调融为一体,大概才看了一节,感觉《具体数学》的确是一本值得仔细研读的好书。
那么下面我们便开始正式开始搬砖工作。
约瑟夫环问题。
这个问题有个很有意思的历史起源:“传说如果不是由于他的数学天赋,约瑟夫不会活到出名的那一 天.在犹太罗马战争期间,他们41名犹太反抗者困在了罗马人包围的洞穴中.这些反抗者宁 愿自杀也不愿被活捉,于是决定围成一个圆圈,并沿着圆圈每隔两个人杀死一个人,直到剩 下后两个人为止.但是,约瑟夫和一个未被告发的同谋者不希望无谓地自杀,于是他迅速 计算出他和其朋友在这个险恶的圆圈中应该站的位置。”
我们先将将这个问题抽象化成数学模型:即将n个人围成一圈,从n开始每隔1个人杀死一个,那么求解最终活下来的那个人的起始编号?
这个问题似乎进行模拟运算是可解的,但是过程太过繁杂丑陋,我们需要一些更加漂亮的数学技巧。
我们以n = 10为例来模拟这个过程,我们绕着圆周筛选一次,则2、4、6、8、10被删除,剩余1、3、5、7、9,为了方便新一轮的筛选,我们此时进行重新编号。
1 -> 1
3 -> 2
5 -> 3
7 -> 4
9 -> 5
我们设置J(n)表示约瑟夫问题的解,通过上述问题的转化我们发现,我们在模拟求解J(n)的时候,生成了一个子问题,而这个子问题又可以转化成另外一个完全独立的问题,也就是在求解J(10)的过程中,我们形成了一个1、3、5、7、9作为环的子问题,而我们又将这个子问题转化成了求解J(5),基于原问题和子问题具有同解性,再基于将子问题转化成J(5)的过程,我们容易建立起J(10)和J(5)的递推关系,即:J(10) = 2J(5) - 1。
由此我们不难进行推广,J(2n) = 2J(n) - 1。
其实讲到这里,如果理解了的话,其实掌握了解决约瑟夫环问题的核心思维了,虽然此时我们对于奇数情况还没有讨论。
我们基于这种思维,进行一个推广,对于n个人,从1开始,每次删掉m的整数倍,该如何求解呢?
类似上文的思路,这次我们缩短我们的脚步,不要一次删掉一圈再进行子问题转化,而删掉一个数后就进行子问题转化。以n = 10,m = 3为例。
第一次删除:3。
删除后的结果:1、2、4、5、6、7、8、9、10。
转化子问题:1 -> 8
2 -> 9
4 -> 1
5 -> 2
6 -> 3
7 -> 4
8 -> 5
9 -> 6
10-> 7
我们容易看到,J(10) = (J(9) + 3) %10,我们也不难找到推广规律——J(n) = (J(n-1) + m)%n。
然后基于J(1) = 1,我们便可以对所有问题进行递推求解。
我们通过一个具体问题来应用一下这个思路。(Problem source : hdu 2925)
Problem Description
In the traditional game of Musical Chairs, N + 1 children run around N chairs (placed in a circle) as long as music is playing. The moment the music stops, children run and try to sit on an available chair. The child still standing leaves the game, a chair is removed, and the game continues with N children. The last child to sit is the winner.In an attempt to create a similar game on these days‘ game consoles, you modify the game in the following manner: N Children are seated on N chairs arranged around a circle. The chairs are numbered from 1 to N . Your program pre-selects a positive number D . The program starts going in circles counting the children starting with the first chair. Once the count reaches D , that child leaves the game, removing his/her chair. The program starts counting again, beginning with the next chair in the circle. The last child remaining in the circle is the winner.
For example, consider the game illustrated in the figure above for N = 5 and D = 3 . In the figure, the dot indicates where counting starts and × indicates the child leaving. Starting off, child #3 leaves the game, and counting restarts with child #4. Child #1 is the second child to leave and counting restart with child #2 resulting in child #5 leaving. Child #2 is the last to leave, and child #4 is the winner. Write a program to determine the winning child given both N and D .
Input
Your program will be tested on one or more test cases. Each test case specifies two positive integers N and D on a single line, separated by one or more spaces, where N, D < 1, 000, 000 .
The last line of the input file contains two 0‘s and is not part of the test cases.
Output
For each test case, write the winner using the following format:
N D W
Where N and D are as above, is a space character, and W is the winner of that game.
容易看到,这道问题基于约瑟夫环问题在数据上做了小小的变动,由于总数是n+1,那么我们便从0开始编号,即初始条件J(1) = 0。