约瑟夫环--数学高效率解法

问题描述

        有n个囚犯站成一个圆圈,准备处决。首先从一个人开始报数,报到m的人被处死,剩下n-1个人继续这个过程,直到最终只剩下一个人留下。问题是:给定了n和m,一开始要站在什么位置才能避免被处决?

  

        对于这个题目最基本的解法是使用循环链表模拟全过程。
#include<iostream>
 
using namespace std;
 
/************约瑟夫问题****************/
 
typedef struct CLinkList
{
    int data;
    struct CLinkList *next;
}node;
 
 
int main()
{
///建立循环链表
    node *L,*r,*s;
    L = new node;
    r =L;
    int n,k;
cin>>n>>k;
for(i = 1;i<=n;i++) //尾插法建立链表 { s = new node; s->data = i; r->next = s; r= s; } r->next =L->next; //让最后一个结点指向第一个有数据结点 node *p; p = L->next; delete L; //删除第一个空的结点 ///模拟解决约瑟夫问题 while(p->next != p) //判断条件:因为最后肯定剩下一个人, 循环链表的最后一个数据的next还是他本身 { for(i = 1;i<k-1;i++) { p = p->next; //每k个数死一个人 } cout<<p->next->data<<"->"; p->next = p->next->next; //将该节点从链表上删除。 p = p->next; } cout<<p->data<<endl; return 0; }

 

        当n,m数据量很小的时候,我们可以用循环链表模拟约瑟夫环的过程。当模拟到人数等于1的时候,输出剩下的人的序号即可。这种方法往往实现起来比较简单,而且也很容易理解。但是时间复杂度却是很糟糕的,达到了O(n*m),因为即使2个人,也要模拟K遍,存在着大量的浪费。在n,m比较大的时候(n*m达到10^8或者更大),那么要得出结果往往需要耗费很长的时间,但是我们可以运用一点数学上的技巧,将最后结果推导出来。
 

        为了讨论方便,先把问题稍微改变一下,并不影响原意:
        问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。

        我们知道第一个出列的人编号一定是 m%n -1。那么剩下的n-1个人便可以看做组成了一个新的约瑟夫环(以编号为k=m%n的人开始):

        k  k+1  k+2  ... n-2, n-1, 0, 1, 2, ... k-2并且从k开始报0。

       现在我们把n-1个人的时候的编号做一下转换(全部减去k):
  k     --> 0
  k+1   --> 1
  k+2   --> 2
  ...
  ...
  k-2   --> n-2

    变换后就完完全全成为了(n-1)个人报数的子问题!(这不是让我们倒推吗?!)

 

  假如我们知道这个子问题的解:例如A[n-1]是在n-1个人中的最终胜利者,那么根据上面对A[n-1]进行转换  便刚好就是n个人情况的解。变回去的公式很简单,如下:

  A[n]=(A[n-1] +k)%n。(k是在n个人中第一个出列的人的编号,此处这个公式涉及了上面叙述到的编号的转换,需要搞懂了才能理解)

 

  由于k=m%n,那么公式也可以写为A[n]=(A[n-1]   +m)%n。为什么呢?很容易理解,7%3=1 , 1%3=1。


  如此也是得出了递推公式:

  A[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是A[n]

  A[1]=0;

  A[2]=(A[1]+m)%2;
  A[i]=(A[i-1]+m)%i;  (i>1)

  有了这个公式,我们要做的就是从1-n顺序算出A[i]的数值,最后结果是A[n]。因为实际生活中编号总是从1开始,我们输出A[n]+1
  由于是逐级递推,不需要保存每个A[i],于是便得到了最终的程序:

#include<iostream>
using namespace std;
int main()
{
    int n,m;
cin>>n>>m;
int winner=0;//总人数n,数到m的倍数离开,最后的人winner for(int i=2;i<=n;i++) winner=(winner+m)%i; cout<<"Winner:"<<winner+1<<endl; }

 

posted on 2020-08-04 15:50  六尺巷人  阅读(372)  评论(0编辑  收藏  举报

导航