约瑟夫环

输入n、m,总人数为n,每数到m杀一个人(这里设第一个序号为 t)

例:n=6, m=2

    1, 2, 3, 4, 5, 6

    1,   , 3, 4, 5, 6

    1,   , 3,   , 5, 6

    1,   , 3,   , 5, 

    1,   ,   ,   , 5, 

      ,   ,   ,   , 5, 

设 f(n) 为第n个人死时,最后活着的人序号,当时有 1个人

设 f( i) 为第 i个人死时,最后活着的人序号,当时有 n-i+1个人

现有,

    f(n) = 1

    f(n-1) = [f(n)+m]%2

    f( i) = [f(i+1) +m]%(n-i+1)

于是,从 f(n)一直推算出 f(1),需要执行 +m)%(n-i+1) 共 n-1 次

 

实现:

    int t = 1;

    for( int i = 2; i <= n; ++i)

    {

        t = (t+m)%i;

    }

    cout << t << endl;

 

注意,这里有一个错误,在 n-1次循环中,我们使用了求余运算,假设除数为i,则取值范围为[0, i-1]

而我们一直的计数是从1到n,而不是 0到n-1,如这还有3个人,我们的计数是 1,2,3,而不是 0,1,2

这就导致了当 t + m == i 时,我们原先的计数方式就使得 t = 0 而不可能取到 t = i,若单独拿出来判断浪费资源

加入判断,

    t = (t+m)%i;

    if( t == 0)

        t = i;

故而,这里我们取从t=0开始计数的计数方式,计算过程不需要判断其它情况,若想得到原来计数方式的结果,+1即可

 

 

 

-------------------------------------------------------------------------------------------------------------------------------

更加明确的思路

 

约瑟夫环问题就是一群人闲得蛋疼坐在一起玩杀人游戏

编号,从1开始与从零开始的区别在于,从 1 开始就需要对序号是否为 0进行判断,若为零,则该名玩家的序号为当前玩家的人数,即序号为最后

 

举例,这里采用从 0 开始编号

    现有 6人,n = 6

    每数到 2,杀一个人,m = 2 

 

游戏进行如下:

    6人 => 杀一人=> 5人 => 杀一人=> 4人 => 杀一人=> 3人 => 杀一人=> 2人 => 杀一人=> 1人

 

我们需要求解的是,最后活下来的那个人,在游戏开始时的序号是多少

(约瑟夫想活下去)

 

设当有 i 名玩家活着时,约瑟夫的序号为 f(i),注意每杀一个人我们将死亡玩家的下一位玩家的序号设为 0

若约瑟夫最后活了下来,则 f(1) = 0

需要求解 f(6) 的值

问题的关键在于 f(i+1) 和 f(i) 的关系

0, 1, 2, 3, 4, 5

4, x, 0, 1, 2, 3

2, x, 3, x, 0, 1

0, x, 1, x, 2, x

1, x, x, x, 0, x

x, x, x, x, 0, x

如上,从 f(i+1) 到 f(i),约瑟夫(每个存活的玩家)的序号 -m)%i,即每杀一个人就 -m并取余当前存活人数 i 保持正值

则 f(i+1) -m = f(i),即 f(i+1) = ( f(i)+m ) % (i+1),因为反推,从 i 人反推到 i+1人状态,存活人数为 i+1

这里共杀了 n-1人剩下约瑟夫

 

int n = 6, m = 2;

int t = 0;

for(int i = 1; i <= n-1; ++i) // [1, 5] 

{

    t = (t+m)%(i+1);

}

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值