关于约瑟夫问题(Josephus Problem)

来源:互联网 发布:可汗学院 黑板软件 编辑:程序博客网 时间:2024/06/04 17:50

这个问题是以弗拉维奥·约瑟夫斯命名的,他是1世纪的一名犹太历史学家。他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中。他们讨论是自杀还是被俘,最终决定自杀,并以报数的方式决定谁杀掉谁。约瑟夫斯说服了一个人,他们将向罗马军队投降,不再自杀。约瑟夫希望知道他们两个人应该怎样坐才能保证两个最终活下来。


换句话说,也就是n个人坐成一个环,从第一个人从1开始报数,报到m的人出局,然后从下一个人开始继续从1开始报数……当n=41m=3的时候的出局顺序如下表示。(还有两个没有标16号和31号就是留下来的位置)
这里写图片描述
图片来源:http://blog.csdn.net/jtlyuan/article/details/7433693


为了取模方便,我们不再从1开始给人标号,我们从0开始顺时针标号。

每次有人出局后,所有人将被重新标号,出局者的下一个人将被标上0,其他人将被依次标上号。

我们考虑一个对应关系f(x)x表示这一轮某人(用A表示)的标号,f(x)则是A在上一轮的标号。

先给出结论:f(x)=(x+m)modi 其中 i 是上一轮还剩下的人数。

我原来想证明这个式子,但是想了一会儿发现这个式子不应该是拿来证明的,可以很好地理解它。A在这一轮的标号是x,那么他在上一轮一定是在出局者往后x+1的位置上(因为我们定义出局者往后一个人为0号,然后依次编号),出局者是报到了m,如果继续从m+1报下去,这个人应该报到了m+x+1,那么很容易计算,在一个从0开始标号的有i个人的环里面,报到m+x+1的人应该是(m+x+11)modi,即结论中给出的公式。

那么对于约瑟夫问题,我们可以知道,当剩下两个人的时候,一个人的编号是0,另一个编号是1。那么通过n2次迭代计算就可以求出刚开始的时候这两个人坐在哪个位置(即初始标号)

部分代码如下:

int t[2];t[0]=0;t[1]=1;for (int i=3;i<=n;i++){    for(int j=0;j<=1;j++){        t[j]=(t[j]+m)%i;    }}

我们换一个问题:如果我们要计算所有人的出局顺序呢?也就是按出局顺序依次给出出局者的初始标号。比如第一张图中的结果就是3,6,9,12,15,18……

一个一个数的复杂度为O(nm),显然是难以接受的,但是通过线段树的优化,我们可以将复杂度降低到O(nlogn)

我们对人数建一颗线段树,每个节点[l,r]保存闭区间[l,r]内还剩下多少人没有出局,刚开始节点[l,r]的值为rl+1

我们先讨论一下n=9,m=4的情况,下图是初始局面:
这里写图片描述

我们定义一个aim,表示我们要找从第一个人开始往后数的第aim个人。

刚开始aim=mmodn
如果aim结果是0,那么aim=n

我们去寻找点(总是先找左节点),当找到[1,5]点时,发现这个节点下有5个人(aim)没有出局,那么第aim个人一定在这个节点下。

进入下一层查找,发现[1,3]节点下有3个人(<aim,此时aim==1,并且我们进入这一层的右节点[4,5]寻找,发现这个节点下有2个人(aim)没有出局,那么第aim个人一定在这个节点下。

我们继续进入下一层节点寻找,发现[4,4]节点下有一个人(aim)所以第aim个人一定在这个节点下,而这个节点又是叶子节点了。所以结果就是4号出局。

之后的局面如下:
这里写图片描述

下一个出局的人应该是第aim=(aim+m1)modval([1,9])个人。
如果aim结果是0,那么aim=val([1,9])

为什么要1呢?可以这样理解,因为出局了第aim个人,那么之后的所有人的相对位置都往前挪了一格。比如原来的第aim+1人,现在就变成了第aim人。按照之前的方法,很容易就可以算出是第7个人出局,也就是8号。

之后的局面如下:
这里写图片描述
下一个出局的人是第3个人,也就是3号,再下一个是第6个人,9号。

代码如下:该代码在CODEVS_1282提交通过

#include<bits/stdc++.h>using namespace std;int read(){    int x=0,f=1;char ch=getchar();    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}    return x*f;}int t[150000];int n,m;void build(int index,int l,int r){    if (l==r){t[index]=1;return;}    int mid=(l+r)>>1;    build(index<<1,l,mid);    build(index<<1|1,mid+1,r);    t[index]=t[index<<1]+t[index<<1|1];}int read(int index,int l,int r,int aim){    if (l==r)   {        t[index]=0;        return l;    }    int mid=(l+r)>>1;    t[index]--;    if (t[index<<1]>=aim)   return read(index<<1,l,mid,aim);    else    return read(index<<1|1,mid+1,r,aim-t[index<<1]);}int main(){    n=read();m=read();    build(1,1,n);    int aim=1;    for (int i=1;i<=n;i++){        aim=(aim+m-1)%t[1];        if(aim==0)  aim=t[1];        printf("%d%c",read(1,1,n,aim)," \n"[i==n]);    }    return 0;}
原创粉丝点击