约瑟夫环-链表-模拟

#1  什么是约瑟夫问题?


讲一个比较有意思的故事:约瑟夫是犹太军队的一个将军,在反抗罗马的起义中,他所率领的军队被击溃,只剩下残余的部队40余人,他们都是宁死不屈的人,所以不愿投降做叛徒。一群人表决说要死,所以用一种策略来先后杀掉所有人。
于是约瑟夫建议:每次由其他两人一起杀一个人,而被kill的人的先后顺序是由抽签决定的,约瑟夫有预谋地抽到了最后一签,在杀了除了他和剩余那个人之外的最后一人,他劝服了另外一个没死的人投降了罗马。

我们这个规则是这么定的:
在一间房间总共有n个人(下标0~n-1),只能有最后一个人活命。)

按照如下规则去排除人:

所有人围成一圈
顺时针报数,每次报到q的人将被排除掉
被排除掉的人将从房间内被移走
然后从被杀掉的下一个人重新报数,继续报q,再清除,直到剩余一人
你要做的是:当你在这一群人之间时,你必须选择一个位置以使得你变成那剩余的最后一人,也就是活下来。
 

#2 求解思路:

特例:2,当q = 2时候,是一个特例,能快速求解
特例还分两种
###1.思路:注意这里的前提是n = 2^k(也就是2的幂次个人,其他数另外讨论)
如果只有2个人,显然剩余的为1号

如果有4个人,第一轮除掉2,4,剩下1,3  ;   3死,留下1

如果是8个人,先除去2,4,6,8,之后3,7,剩下1,5,除去5,又剩下1了
1
2
3
……

定义J(n)为n个人构成的约瑟夫环最后结果,则有j(2^k) = 1

J(n) = 2^k – 2^k-1 = 2^k-1                     n=2^k

J(n) = 2^k-1 – 2^k-2 = 2^k-2                n=2^k-1

………

J(2^2) = 2^2 – 2^1 = 2^1                     n=2^2
1
2
3
4
5
6
7
递推得到如上结果,起始我们仔细分析也就是每次除去一半的元素,然后剩余的一半继续重复之前的策略,再除去一半。(可想到递归)

结合:J(2) = 1 我知道两个数,从1开始,肯定是2先死,剩下1.

得到:j(2^k) = 1

###2.but当n 不等于 2^k时候,比如9,11这种:

n 不等于 2^k时,就不存在这样的easy的规律了,重新分析:

假设n = 9,这时候如图下:


能看出来,我们干掉第一个人也就是2,之后就只剩下8个人了,又回到J(2^k)上了,这时候我们需要的是找到当前的1号元素。
见图下:


这时候,我们从3号开始,就成了另外一个规模小1的约瑟夫问题(恰好为2^k的特例)。
这时候,我们可以把3号看成新的约瑟夫问题中的1号位置:
J(8) = J(2^3) = 1,也就是说这里的1代表的就是上一个问题中的3号

So:J(9) = 3
答案为3

####同理可知所有的非2^k的数都是这样:
假设n = 2^k + t,t可以随意取,比如1,2,3…….

假设n = 11,这时候n = 2^3 + 3,也就是说t = 3,所以开始剔除元素直到其成为2^k问题的约瑟夫问题。
So,我们在剔除了t(3)个元素之后(分别是2,4,6),此时我们定格在2t+1(7)处,并且将2t+1(7)作为新的一号,而且这时候的约瑟夫环只剩下23,也就是J(2^3 + 3) = 2*3 + 1 = 7,答案为7

总结一下这个规律:
J(2^k + t) = 2t+1;

##3.说完了特例,现在说说q 不等于2的情况下:

当q ≠ 2:

我们假定:

n — n人构成的约瑟夫环
q — 每次移除第q个人
约定:
Jq(n)表示n人构成的约瑟夫环,每次移除第q个人的解
n个人的编号从0开始至n-1
我们沿用之前特例的思想:能不能由Jq(n+1)的问题缩小成为J(n)的问题(这里的n是n+1规模的约瑟夫问题消除一个元素之后的答案),Jq(n)是在Jq(n+1)基础上移除一个人之后的解。也就是说,我们能由Jq(n)得到Jq(n+1)。

规律:Jq(n+1) = ( Jq(n) + q ) / (n+1)
 例题:

P1996 约瑟夫问题

提交99.46k

通过52.92k

时间限制1.00s

内存限制125.00MB

题目描述

n 个人围成一圈,从第一个人开始报数,数到 m 的人出列,再由下一个人重新从 1 开始报数,数到 m 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。

输入格式

输入两个整数 n,m

输出格式

输出一行 n个整数,按顺序输出每个出圈人的编号。

输入输出样例

输入 #1复制

10 3

输出 #1复制

3 6 9 2 7 1 8 5 10 4

说明/提示

1≤m,n≤100

先把代码贴上:

#include<iostream>
#include<cstdio>
using namespace std;
int m,n,k,a[1009];
int main()
{
    cin>>m>>n>>k;
    for(int i=0;i<m;i++)
        a[i]=i+1;
    int p=0;//起始点
    for (;m>0;--m)
    {
        p=(p+n%m-1+m)%m;
        cout<<a[p]<<" "[m==1];
        copy(a+p+1,a+m,a+p);//(O_O)?
    }
    return 0;
}

注释已经基本写出思路,详细解释两点:

1.p=(p+n%m-1+m)%m;很多人问这是什么,我给大家解释一下,其实所有约瑟夫杀人问题都可以套这个公式。

p相当于指针,指向下一个要被杀的人;

n%m是由于n可能会比m打,为了减小运算量,对他先取余;

再加上p是由于这一回要从p的位置开始数,所以+p;

减去1是因为p本身也数,我们多数了一个,所以减去1;

只要有减法就可能会出现负数,防止越界要再多数一圈,也就是加m;

最后再对m取余,得出p——下个被杀出圈的人。

2.copy(a+p+1,a+m,a+p);大家更好奇的也许是这个小东西,是时候让他闪亮登场啦!【鲜花,掌声】

我们先声明一个数组a,copy需要三个参数。例如:copy(a+1,a+i+1,a+i);表示把a+1到a+i+1这区间的数拷贝到首地址是a+i的数组元素中,其原来的数值将被覆盖。

那么copy(a+p+1,a+m,a+p);大家就不难理解了,在这里正好起到了一个把刚被杀死的人从圈里踢出的作用。正好循环里写的是m--,同时圈内的人数也减少了一个,不影响运算。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

litian355

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值