Description
Suppose that there are k good guys and k bad guys. In the circle the first k are good guys and the last k bad guys. You have to determine such minimal m that all the bad guys will be executed before the first good guy.
Input
Output
Sample Input
3 4 0
Sample Output
5 30
该问题是Joseph环(约瑟夫环)的变式题,先回顾下约瑟夫环问题。
一、 约瑟夫环问题
已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。最后一个人为胜利者。
为分析方便n个人编号0 ~ n-1,报数0 ~ m-1
2. 问题分析
有i个人,报数0~m-1,编号如下:
第一个出局者易知为(m-1)%i, 记为k-1。最后获胜者记为f(i)。
第一个人出局后有i-1个人,编号如下:
最后获胜者记为f(i-1)。
如果知道f(i-1)便可求的f(i),根据编号对应关系得:f(i) = [f(i-1) +k] %i (1)
而f(1) = 0,结合(1)可求出f(2), f(3), …, f(n)
3. 代码
#include <stdio.h> int main() { int n, m, i, winner = 0; scanf("%d%d", &n, &m); for (i=2; i<=n; ++i) { winner = (winner + m) % i; } printf("winner = %d(0~n-1)\n", winner); return 1; }
二、 约瑟夫环变式问题(POJ1012)
1. 问题描述
n个人,前2/n个是好人,后2/n个人是坏人,寻找最小的m先让2/n个坏人先出去。
为分析方便n个人编号0 ~ n-1,报数0 ~ m-1
2. 问题分析
有n个人,报数0~m-1,编号如下
第一个出局者易知为 f(1)=(m-1)%n,记为k-1。
第一坏人出局后,剩下i-1个人,编号如下
第二个出局者易知为 (m-1)%(n-1),
从左至右编号,对应为
f(2)=[(m-1)%(n-1) + f(1)] % (n-1)
=[m-1 + f(1)] % (n-1)
第i个出局者从左至右对应编号为:f(i) =[(m-1) + f(i-1)] % (n –i + 1),其中f(0)= 0。
该问题求解思路便是枚举m,测试第1个到第2/n个出局者编号 f(i) >= 2/n。
3. m枚举的优化
关于m的枚举,考虑只剩最后一个坏人记为X时,需要让该坏人(m-1) % (2/n +1)出局,而报数开始位置需要先找倒数第二个坏人记为B出局的位置,而B的位置只能为以下两种情况:
则可以知道最后一个坏人X出局位置(m-1)%(2/n + 1)= 0或者2/n,可简化为
m% (2/n+ 1) = 0或者1。
4. 代码
#include <stdio.h> int a[14]; int main() { int k, m, i, j, n; for (k=1; k<14; ++k) { if (k == 0) break; n = 2 * k; m = k + 1; while (m) { for (i=0, j=0; i<k; ++i) { j = (m - 1 + j) % (n - i); if (j < k) break; } if (i == k) { a[k] = m; break; } // m % (k + 1) = 0或者1 if (m %(k + 1) == 0) ++m; else m += k; } } while (scanf("%d", &k)) { if (!k) break; printf("%d\n", a[k]); } return 0; }
最后再谈谈关于POJ1012这题的注意事项:
1. 不使用直接暴力计算依次报数,肯定超时;
2. 0<k<14,所以可以使用打表。。。
// 暴力打表 #include <stdio.h> int a[30]; int main() { int k; while (scanf("%d", &k)) { if (k == 0) break; switch(k) { case 1: printf("2\n");break; case 2: printf("7\n");break; case 3: printf("5\n");break; case 4: printf("30\n");break; case 5: printf("169\n");break; case 6: printf("441\n");break; case 7: printf("1872\n");break; case 8: printf("7632\n");break; case 9: printf("1740\n");break; case 10: printf("93313\n");break; case 11: printf("459901\n");break; case 12: printf("1358657\n");break; case 13: printf("2504881\n");break; } } return 0; }
3. 就算优化了计算过程,使用递推公式 f(i) = [(m-1) + f(i-1)] % (n – i + 1) 来计算出局人编号,依然存在超时可能,因为其测试数据存在多个重复数据,比如1000个13。。。所以需要先计算1-13对应的值存储起来,再按输入给出输出。