约瑟夫环问题

题目描述:

已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为s的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列,求最后一个出列人的编号。

 

分析:

先考虑从第一个人开始报数的情况;

如果考虑从1开始编号,则编号为k的人出列之后,下一个要出列的人的编号为:(k+m-1)%n+1,于是,如果从x开始编号的话,下一个拖出去受死的人的编号就应该是:(k+m-x)%n+x了;

下面选择从0开始编号:

先标记这n个数,从0开始到n-1;

编号为k的人出列后,下一个要出列的人的编号为:(k+m)%n (假设当前有n个人还在圆桌中);

从0编号开始报数,第一个出环的人m%n-1,剩下的n-1个人组成一个新的约瑟夫环,接下来从m%n开始报数,令k=m%n,新环表示为:

k, k+1, k+2, ……n-1, 0, 1, 2, …..., k-2

我们对此环重新编号,其实编号并不会影响实际结果。

k    à 0
k+1 à 1
k+2 à 2
...
k-2 à n-2

对应关系为:x’ = (x+k)%n (其中,x’是左侧的,x是右侧重新编号的)(通过观察可得)

重新编号之后,就成了求n-1约瑟夫环问题了,貌似差不过已经解决了,我们递归求解就ok了~

即:f[n] = (f[n-1]+m%n)%n,化简为:f[n]=(f[n-1]+m)%n;并且f[1]=0;

从而有:

int main()
{
int n, m, i, k=0;
scanf("%d%d", &n, &m);
for (i=2; i<=n; i++) k=(k+m)%i;
printf ("%d\n", k);
}

 

 

下面就可以考虑从第s个人开始报数的情况了:

其实我们从s开始考虑,只要多转化一不就行了,即:

s    à 0
s+1 à 1
s+2 à 2
...
s-2 à n-2

s-1à n-1

所以,如果从0开始编号并且从0编号的人开始报数的n个人的约瑟夫环问题的答案为k的话,则从0开始编号并且从s编号的人开始报数的n个人的约瑟夫环问题的答案就是:(s+k)%n

 

 

coj1012 ac code:

#include<iostream>
using namespace std;

int main()
{
int n,s,m;
while(cin>>n>>s>>m)
{
int k=0;
for(int i=2;i<=n;i++)
{
k=(k+m)%i;
}
//cout<<k<<endl;k即为如果从第一个人开始报数的结果:
s=(s+k)%n;
cout<<s<<endl;//s为从第s个人开始报数的结果;
}
return 0;
}

 

 

对于coj1015 与1012 相似,题意:

k个好人与k个坏蛋站一圈,前k个都是好人,从1开始报数,报道m的枪毙,下一个再从1开始报数,以此类推!求一个满足条件的数m,当剩下k个人时,满足他们都是好人

做法与上题类似;

一开始就TLE 了:

#include<iostream>
using namespace std;
int fn(int k,int m)
{
int length,i,s;
length=2*k;
s=0;
for(i=1;i<=k;i++)
{
s=(s+m-1)%(length-i+1);
if(s<k)return 0;//遇到前k轮中有小于k的直接返回0;
}
return 1;//如果前k轮中无小于k的情况,则返回1;
}
int main()
{
int k,n;
__int64 a[20];
for(k=1;k<=20;k++)
{
for(int m=k+1; ;m++)
{
if(fn(k,m))
{
a[k]=m;
break;
}
}
}
while(cin>>n)
{
if(n==0)break;
printf("%I64d\n",a[n]);
}
return 0;
}

 

然后用暴力的方法把前20个都算出来了;

ac code:

#include<stdio.h>
__int64 a[19]={2,7,5,30,169,441,1872,7632,1740,93313,459901,1358657,2504881,13482720,25779600,68468401,610346880,1271932200,327280800};
int main()
{
int i;
while ( scanf("%d",&i), i != 0 )
printf("%I64d\n",a[i-1]);
return 0;
}

oh,shit!!!