【原】 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: }