转自:打开链接
题目描述:30个游客同乘一条船,因为严重超载, 加上风浪大作,危险万分。因此船长告诉乘客,只有将全船 一半的旅客投入海中,其余人才能幸免于难。无奈,大家只 得同意这种办法,并议定30 个人围成一圈,由第一个人数起,依次报数,数到第9人,便把他投入大海中,然后再从 他的下一个人数起,数到第9人,再将他投入大海中,如此 循环地进行,直到剩下 15 个游客为止。问:哪些位置是将 被扔下大海的位置?
这道题目的难点在于30个人会围成一个圈
举个例子有十个人,报数到四的时候我们把他丢下去
开始时:0 1 2 3 4 5 6 7 8 9
第一次:0 1 2 4 5 6 7 8 9
第二次:0 1 2 4 5 6 8 9
第三次:0 2 4 5 6 8 9
....
...
最终会留下编号为4的人.
拿开始时和第一次进行分析:
第一次这些编号已经不能组成一个环,但是可以看出4至2之间还是连着的(4 5 6 7 8 9 0 1 2),且下一次报数将从4开始。但是,之后的报数将总要考虑原编号3处的空位问题。
如何才能避免已经产生的空位对报数所造成的影响呢?
可以将剩下的9个连续的数组成一个新的环(将2、4连接),这样报数的时候就不用在意3的空位了。但是新产生的环的数字并非连续的,报数时不像之前那样好处理了(之前没人被扔海里时下一个报数的人的编号可以递推,即(当前编号+1)%sum ),无法不借助存储结构得知下一个应该报数的现存人员编号。
如何使新环上的编号能够递推来简化我们之后的处理呢?
可以建立一种有确定规则的映射,要求映射之后的数字可以递推,且可以将在新环中继续按原规则报数得到的结果逆推出在旧环中的对应数字。
方法:将它与 sum-1 个人组成的(0 ~ sum-1)环一 一映射。
比如之前的栗子,将剩余的 9 人与 9 人环(0~8)一 一映射。既然 3 被扔到海里之后,报数要从4开始 (4 其实在数值上等于最大报数值),那么就将4映射到0~8的新环中0的位置,也就是说在新环中从0开始报数即可,且新环中没有与3对应的数字,因此不必担心有空位的问题。从旧环的 4 开始报数等效于从新环中的 0 开始报数。
开始时:0 1 2 3 4 5 6 7 8 9
第一次: 6 7 8 0 1 2 3 4 5
如何由新环中的 3 得到旧环中的 7 呢。其实可以简单地逆推回去 : 新环是由 (上一次编号-最大报数值)%总人数 得到的,所以逆推时可以由 ( 下一次数字 + 最大报数值 )% 上一次总人数 取得。
如 : ( 3 + 4 ) % 10 =7 .
也就是说在,原序列( sum ) 中第二次被扔入海中编号可以由新序列( sum - 1) 第一次扔海里的编号通过特定的逆推运算得出。
而新序列 (sum -1)也是(从0开始)连续的,它的第二次被扔入海中的编号由可以由(sum - 2)的第一次扔入海里的编号通过特定的逆推运算得出,并且它的第二次被扔入海中的编号又与原序列中的第三次被扔入海里的编号是有对应关系的。
也求是说有以下推出关系:
(sum-2)环的第1次出环编号 >>>(sum-1)环的第2次出环编号 >>>(sum)环的第3次出环编号
即 在以 k 为出环报数值的约瑟夫环中, m人环中的第n次出环编号可以由 (m-1) 人环中的第 (n-1) 次出环编号通过特定运算推出
由图知,10人环中最后入海的是4号,现由其在1人环中的对应编号0来求解。
出环次数
#include <stdio.h>
#include <math.h>
#include <iostream>
int ysfdg(int sum,int value,int n)
{
if(n==1)
return (sum+value-1)%sum;
else
return (ysfdg(sum-1,value,n-1)+value)%sum;
}
int main()
{
int sum=0,count=0,alive=0,i=0;
//读入总人数,报数值,存活人数
cin>>sum>>count>>alive;
for(i=1;i<=sum-alive;i++)
printf("第%2d个被扔海里人的编号:%2d\n",i,ysfdg(sum,count,i)+1);
return 0;
}