约瑟夫斯问题
约瑟夫斯问题(有时也称为约瑟夫斯置换),是一个出现在计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。
有 n 个囚犯站成一个圆圈,准备处决。首先从一个人开始,越过 k − 2 个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过 k − 1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。
问题是,给定了 n 和 k ,一开始要站在什么地方才能避免被处决?
————源自维基百科
比较简单的做法是用循环单链表模拟整个过程,时间复杂度是O(n*m)。如果只是想求得最后剩下的人,则可以用数学推导的方式得出公式。
公式推导如下:
#include <iostream> using namespace std; //編號從0開始,也就是說如果編號從1開始結果要加1 int josephus(int n, int k) { //非遞回版本 int s = 0; for (int i = 2; i <= n; i++) s = (s + k) % i; return s; } int josephus_recursion(int n, int k) { //遞回版本 return n > 1 ? (josephus_recursion(n - 1, k) + k) % n : 0; } int main() { for (int i = 1; i <= 100; i++) cout << i << ' ' << josephus(i, 5) << ' ' << josephus_recursion(i, 5) << endl; return 0; }
维基百科所给的解释已足够清楚,在此仅作学习笔记。
以下是约瑟夫问题的变式:
约瑟夫环
时间限制: 1 Sec 内存限制: 128 MB
题目描述
约瑟夫的问题是非常出名的。从N个人中,编号为1,2,。。,N,站成一圈报数,每个报到M的人都会被枪决,只有最后剩下的人能够活命。约瑟夫很聪明,他选择了活命者的位置,给我们留下了这个故事。
例如,当N=6,M=5,那么被杀的顺序是5,4,6,2,3,1
现在假设有k个好人和k个坏人。在圈内的前k个是好人,后k个是坏人。
请你确定一个最小的m,使得好人被杀前,坏人全部都被杀掉。输入
每一行输入一个k值,输入为0时,结束(0<k<14)输出
输出能够满足的M样例输入
3 4 0
样例输出
5 30 这个问题我最开始选择的是链表的模拟算法,如下:#include<cstdio> #include<cmath> #include<cstring> #include<iostream> #include<iomanip> #include<cstdlib> #include<algorithm> #include<string> #include<vector> #include<queue> #include<stack> #include<set> #include<list> using namespace std; const int N = 2500000; int res[14] = { 0,2,7,5,30,169,441,1872,7632,1740,93313,459901,1358657,2504881 }; void solu() { list<int> joesph; list<int>::iterator it; int m = 0, n = 0; for (int k = 1; k < 14; k++) { n = k * 2; int flag = 1; for (m = n / 2 + 1; flag && m < N; m++)//从小的数开始遍历,找到即可以退出 { if (!(m % (k + 1) == 0 || m % (k + 1) == 1))continue; //cout << "当杀人方法为杀第" << m << "个人时" << endl; joesph.clear(); for (int i = 1; i <= n; i++)//初始化列表 joesph.push_back(i); it = joesph.begin(); for (int i = 1; i < m; i++)//报数 { it++; if (it == joesph.end())it = joesph.begin();//循环操作 } while (*it > n / 2)//开始杀好人时结束循环 { //cout << "杀了序号为" << *it << "的人" << endl << endl; it = joesph.erase(it); if (it == joesph.end())it = joesph.begin();//循环操作 for (int i = 1; i < m; i++)//报数 { it++; if (it == joesph.end())it = joesph.begin();//循环操作 } } flag = 0; for (it = joesph.begin(); it != joesph.end(); it++) { if (*it > n / 2)flag = 1;//如果还有坏人就标记,进行下一个循环 } } /*for (it = joesph.begin(); it != joesph.end(); it++) { cout << *it << endl; }*/ res[n] = m - 1; cout << n << " " << m - 1 << endl; } } int main() { solu(); int k; while (cin >> k, k) cout << res[k] << endl; return 0; }
但是很明显,这个方法耗时太长,才到7或者8就需要几秒钟到几十分钟。所以这种方法是不太合适的。这就需要用到数学的推导方法。
根据之前的数学推导,可以得到:#include<stdio.h> int ans[14] = { 0 }; int joseph(int k) { int cnt, p; if (ans[k]) return ans[k]; for (int i = k + 1;; i++) { for (cnt = 2 * k, p = 0; cnt>k; cnt--) //检查是否满足要求 { p = (p + i - 1) % cnt; //被杀掉的人的位置 if (p < k) cnt = 0; //被杀掉的人是前k个(好人) } if (cnt == k) //所有坏人都被杀了,满足要求 { ans[k] = i; return i; } } return 0; } int main() { int n; while (scanf("%d", &n) != EOF && n) { printf("%d\n", joseph(n)); } return 0; }
参考与转载来源:
https://zh.wikipedia.org/wiki/%E7%BA%A6%E7%91%9F%E5%A4%AB%E6%96%AF%E9%97%AE%E9%A2%98
http://blog.youkuaiyun.com/u013446688/article/details/43058247