【原】 POJ 3750 小孩报数问题 Joseph相关问题详解 解题报告

 

http://poj.org/problem?id=3750

 

方法:

1012类似,但稍复杂

约瑟夫环问题:三个参数————人数n,开始人的号start,数到step出队一人

此问题一般有两种形式(这里都给了实现)

1、求出队顺序:利用数组或循环链表模拟出队过程,复杂度为O(step*n)
     技巧:循环链表实现时将初始指针指向start之前的节点,这样使得代码简便,并且容易处理step为1的情况
     线段树搞到O(n * logn)或树状数组优化到O(n * logn * logn) ????????????????

2、求最后出队的人:解此问题不需要模拟出队过程,复杂度降为O(n)

     思路:

     <1> start = 1
     初始n环为:1 2 3 4 ... n-1 n
     当n个人围成一圈并以m为步长第一次报数时,第m个人出列,此时就又组成了一个新的人数为n-1的约瑟夫环,从m+1开始。
     此时n-1环为:m+1 ... n-1 n 1 2 3 ... m-2 m-1 
     现在将环中编号做一个转化:
     m+1 --> 1 , m+2 --> 2 , ...... m-2 --> n-2 , m-1 --> n-1
     这又形成了一个从1开始的n-1的环,如果我们求得此环最后一个出队的编号为P(n-1,m),它也是初始n环最后一个出队元素,
     但是与它在初始n环中的编号不同,所以我们需要将它在n-1环中的编号变换回来,设原始编号为P(n,m),
     则P(n,m)=( P(n-1,m) + m-1 )%n+1,此处不写成( P(n-1,m) + m )%n是为了避免余数为0的情况。
     如何知道(n-1)个人报数的问题的解?只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况......

     递归式:P(i,step) = 1 , i==1
             P(i,step) = ( P(i-1,step)+step-1 )%i+1 , i>1
     P(n,step)求得起始编号为1时的最后出队编号。

     技巧:
     递归式中+step-1是为了避免余数为0的情况
     
     ****************************************

     <2> start任意(假设start<=n,否则start%=n)
     初始n环为:1 2 3 4 ... n-1 n
     第一个出列的编号为(start+m-1)
     此时n-1环为:start+m  start+m+1 ... n-1 n 1 2 3 ... start+m-2

     递归式:P(i,1,step) = 1 , i==1
             P(i,1,step) = ( P(i-1,1,step)+step-1 )%i+1 , 1<i<n
             P(n,start,step) = ( P(n-1,1,step)+(start+step-1)-1 )%n+1 , i==n (因为变换后的起始编号为1)
     P(n,start,step)求得起始编号为start时的最后出队编号。

 

 

Description

有N个小孩围成一圈,给他们从1开始依次编号,现指定从第W个开始报数,报到第S个时,该小孩出列,然后从下一个小孩开始报数,仍是报到S个出列,如此重复下去,直到所有的小孩都出列(总人数不足S个时将循环报数),求小孩出列的顺序。

Input

第一行输入小孩的人数N(N<=64)
接下来每行输入一个小孩的名字(人名不超过15个字符) 
最后一行输入W,S (W < N),用逗号","间隔

Output

按人名输出小孩按顺序出列的顺序,每行输出一个人名

Sample Input

5

Xiaoming

Xiaohua

Xiaowang

Zhangsan

Lisi

2,3

Sample Output

Zhangsan

Xiaohua

Xiaoming

Xiaowang

Lisi



   1: #include <stdio.h>
   2: #include <iostream>
   3: #include <string>
   4: #include <fstream>
   5:  
   6: using namespace std ;
   7:  
   8: //利用数组求出队顺序
   9: void run3750()
  10: {
  11:     ifstream in("in.txt");
  12:  
  13:     int n,start,step ; //三个参数分别是人数,开始人的号,数到step出队一人
  14:     int i,j ;
  15:     int left ;
  16:     string name ;
  17:  
  18:     in>>n ;
  19:     //1...n
  20:     //存放于位置对应的人名
  21:     string *strArr = new string[n+1] ;
  22:     //记录该位置上是否还有人
  23:     //1:有人
  24:     //0:已出队
  25:     int *numArr = new int[n+1] ;
  26:     for( i=0 ; i<=n ; ++i )
  27:         numArr[i]=1 ;
  28:  
  29:     i = 1 ;
  30:     while( i<=n && in>>name )
  31:         strArr[i++] = name ;
  32:     scanf("%d,%d",&start,&step);  //scanf可以格式化输入
  33:  
  34:     i = start ;
  35:     j = 0 ;
  36:     left = n ;
  37:     while( left > 0 )
  38:     {
  39:         if(numArr[i]==1)
  40:             ++j ;
  41:         if(j==step)
  42:         {
  43:             numArr[i] = 0 ;
  44:             --left ;
  45:             j = 0 ;
  46:             cout<<strArr[i]<<endl ;
  47:         }
  48:         /* 这样会造成当numArr[1...n]都为0(都出队之后)时的死循环
  49:         do
  50:         {
  51:             if(++i==n+1)
  52:                 i = 1 ;
  53:         }
  54:         while(numArr[i]==0) ;
  55:         */
  56:         if(++i==n+1)
  57:             i = 1 ;
  58:     }
  59:  
  60:     delete []numArr ;
  61:     delete []strArr ;
  62: }
  63:  
  64:  
  65: //利用循环链表求出队顺序
  66: //技巧:将初始指针指向start之前的节点,这样使得代码简便,并且容易处理step为1的情况
  67: struct node
  68: {
  69:     string name ;
  70:     node *next ;
  71: };
  72:  
  73: void JosephusLinkList()
  74: {
  75:     ifstream in("in.txt");
  76:  
  77:     int n,start,step ; //三个参数分别是人数,开始人的号,数到step出队一人
  78:     int i ;
  79:     node *first,*last ;
  80:     node *p,*q ;
  81:  
  82:     in >> n ;
  83:     //先构造一个节点的循环链表,设置好first和last指针
  84:     //这是必须的,不能放在循环中做,要单独做
  85:     first = new node ;
  86:     in >> (first->name) ;
  87:     first->next = first ;
  88:     last = first ;
  89:  
  90:     //再构建2~n节点
  91:     for(i=2 ; i<=n ; ++i)
  92:     {
  93:         last = ( last->next = new node ) ;
  94:         in >> last->name ;
  95:         last->next = first ;
  96:     }
  97:  
  98:     scanf("%d,%d",&start,&step);  //scanf可以格式化输入
  99:  
 100:     //找到起始点,为start之前的节点,所以p初始化为last而不是first
 101:     //p = first ;
 102:     p = last ;
 103:     for( i=1 ; i<start ; ++i )
 104:         p = p->next ;
 105:  
 106:     //模拟出队过程
 107:     while( n>0 )
 108:     {
 109:         //找到待出队节点之前的节点
 110:         for( i=1 ; i<step ; ++i )
 111:             p = p->next ;
 112:         //记录下将出队的节点,以备输出和删除
 113:         q = p->next ;
 114:         //出队
 115:         p->next = p->next->next ; 
 116:         //输出
 117:         cout<<q->name<<endl ;
 118:         //删除
 119:         delete q ;
 120:         --n ;
 121:     }
 122: }
 123:  
 124:  
 125: //求最后出队的人,复杂度O(n)
 126: //起始编号为1时最后出队的编号
 127: int Josep1( int n, int step )
 128: {
 129:     /*
 130:     //递归
 131:     if(n==1)
 132:         return 1 ;
 133:     return ( Josep(n-1,step)+step-1 ) % n + 1 ;
 134:     */
 135:     
 136:     //非递归
 137:     int lastOut = 1 ;
 138:     for( int i=2 ; i<=n ; ++i )
 139:         lastOut = (lastOut+step-1)%i+1 ; //+step-1是为了避免余数为0的情况
 140:     return lastOut ;
 141: }
 142:  
 143: //求最后出队的人,复杂度O(n)
 144: //起始编号为start时最后出队的编号,复杂度O(n)
 145: int Josep2( int n, int start, int step )
 146: {
 147:     /*
 148:     //递归
 149:     if(n==1)
 150:         return 1 ;
 151:     return ( test(n-1,1,step)+(start+step-1)-1 ) % n + 1 ;
 152:     */
 153:  
 154:     //非递归
 155:     int lastOut = 1 ;                     //i==1
 156:     for( int i=2 ; i<=n-1 ; ++i )         //2<=i<=n-1
 157:         lastOut = (lastOut+step-1)%i+1 ;
 158:     return ( lastOut + (start+step-1) - 1 )%n+1 ;  //i==n
 159: }
posted @ 2010-11-09 20:06  Allen Sun  阅读(711)  评论(0编辑  收藏