约瑟夫环问题

先看下资料,数学公式来的

资料转自:http://jackiesteed.blog.163.com/blog/static/165506750201051552930758/

约瑟夫环问题是一道经典的数据结构题目
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。
一般我们采用一个循环队列来模拟约瑟夫环的求解过程,但是如果n比较大的时候,采用模拟的方式求解,需要大量的时间来模拟退出的过程,而且由于需要占用大量的内存空间来模拟队列中的n个人,并不是一个很好的解法。
在大部分情况下,我们仅仅需要知道最后那个人的编号,而不是要来模拟一个这样的过程,在这种情况下,可以考虑是否存在着一种数学公式能够直接求出最后那个人的编号。

我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):
我们先看第一个人出列后的情况,显而易见,第一个出列的人的编号一定是m%n-1,这个人出列后,剩下的n-1个人组成了一个新的约瑟夫环,这个约瑟夫环的第一个人在最开始的环中的编号是k=m%n(就是第一个出列的人的下一个)
k  k+1  k+2  ... n-2, n-1, 0, 1, 2, ... k-2并且从k开始报0。
事实上,可以把这个环又映射成为一个新的环:
k  --- 0
k+1 --- 1
k+2 --- 2
...  ....
k-2 -- n-1
可以看出,这就是原问题中把n替换成n-1的情况,假设我们已经求出来在这种情况下最后胜利的那个人的编号是x,那个倒推回去的那个人的编号就正好是我们要求的答案,显而易见,这个编号应该是(x+k)%n
那么如何知道n-1个人下面的这个x呢,yes,就是n-2个人情况下得到的x'倒推回去,那么如何知道n-2情况下的x'呢,当然是求n-3个人,这就是一个递归的过程
f(1) = 0(f(1)就是现在还剩下1个人,那么无论m为几,这个人总会出列,因此f(1)=0)
f(n) = (f(n-1)+m)%n
那么我们要求f(n),就从f(1)倒推回去即可
int f(int n, int m)
{
   int r = 0;
   for(int i = 2; i <= n; i++)
       r = (r + m) % i;
   return r + 1; //这是因为日常生活中编号总是从1开始
}

pku3715 And Then There Was One

基本的约瑟夫环问题,可以直接用数组模拟,也可以用数学方法做,当然,时间差很大

pku 3715
数学方法
#include
<iostream>
using namespace std;
int main()
{
int n,k,m;
while(scanf("%d %d %d",&n,&k,&m),n||k||m)
{
int i,d,s=0;
for(i=2;i<=n;i++) s=(s+k)%i;
k
=k%n;if(k==0) k=n;
d
=(s+1)+(m-k);
if(d>=1 &&d<=n) printf("%d\n",d);
else if(d<1) printf("%d\n",d+n);
else if(d>n) printf("%d\n",d%n);
}
return 0;
}
------------------------------------------------------------------------------
数组模拟
#include
<iostream>
using namespace std;
int a[10001];
int main()
{
int n,k,m;
while(scanf("%d %d %d",&n,&k,&m),n||m||k)
{
int i,cur;
for(i=0;i<m-1;i++) a[i]=i;
for(;i<n;i++)a[i]=i+1;
cur
=m-1;
while(--n){
cur
=(cur+k-1)%n;
for(i=cur+1;i<n;i++)
a[i
-1]=a[i];
}
printf(
"%d\n",a[0]+1);
}
return 0;
}

hdu 1443 Joseph

可以先打表,在求m值的时候,可以枚举每一种可能

hdu 1443
#include<stdio.h>
int f[15];
bool jos(int ,int );
int main()
{
int i,j,k,m,n;
for(i=1;i<14;i++)
for(j=i;;j++)
if(jos(i,j))
{
f[i]
=j;
break;
}
while(scanf("%d",&n),n)
printf(
"%d\n",f[n]);
return 0;
}
bool jos(int n,int m)//n代表每一边的人数,m代表所报的数
{
int start=0,end=n-1,killed;
int i,j;
bool flag=true;
for(i=2*n;i>n;i--)//从头进行模拟
{
killed
=(m-1)%i;//i代表剩余的人数,killed表示出列的相对编号
if((killed<=end)&&(killed>=start))
{
flag
=false;
break;
}
//重新修改符号条件的范围
start=((start-m)%i+i)%i;//按照那个编号变化公式重新进行赋值
end=((end-m)%i+i)%i;//加上i,再%i的目的是保证它非零
}
return flag;
}
原文地址:https://www.cnblogs.com/nanke/p/2168796.html