约瑟夫环问题
前言
约瑟夫环问题是一个很很经典的问题,问题的描述大概如下:这个数字排成一个圆圈,从数字开始,每次从这个圆圈里删除第个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
可以用队列来模拟或者是递推来解决这个问题。
玩游戏
个小朋友围成一圈,玩数数游戏。
小朋友们按顺时针顺序,依次编号为 。
初始时, 号小朋友被指定为领头人。
游戏一共会行进 轮。
在第 轮中,领头人会从他的顺时针方向的下一个人开始,按顺时针顺序数 个人。
其中,最后一个被领头人数到的人被淘汰出局,这也意味着该轮游戏结束。
出局者的顺时针方向的下一个人被指定为新领头人,引领新一轮游戏。
例如,假设当游戏即将开始第 轮时,还剩下 个小朋友,编号按顺时针顺序依次为 ,并且当前领头人为 号小朋友,,则第 轮游戏结束后,最后一个被数到的小朋友为 号小朋友,他将被淘汰出局,并且处于其下一位的第 号小朋友将被指定为新领头人。
现在,请你求出每一轮次被淘汰的小朋友的编号。
输入格式
第一行包含两个整数 。
第二行包含 个整数 。
输出格式
一行, 个整数,其中第 个整数表示在第 轮中被淘汰的小朋友的编号。
数据范围
前三个测试点满足 。
所有测试点满足 ,,。
输入样例1:
7 5 10 4 11 4 1
输出样例1:
4 2 5 6 1
输入样例2:
3 2 2 5
输出样例2:
3 2
解题思路
数据范围比较小,所有可以直接开个队列来模拟。一开始先将按顺序放到队列中,每次都从队头开始数,每数到一个数就把这个数放到队尾,因此队列就起到一个循环枚举的作用。当数完个数后,此时队头的数就是要删除的数,把这个数从队头删除。
的取值范围很大,但由于是环形的枚举,因此就可以去掉重复的循环枚举,为最终枚举的次数。因此时间复杂度为。
AC代码如下:
1 #include <cstdio> 2 #include <queue> 3 #include <algorithm> 4 using namespace std; 5 6 queue<int> q; 7 8 int main() { 9 int n, m; 10 scanf("%d %d", &n, &m); 11 for (int i = 1; i <= n; i++) { 12 q.push(i); 13 } 14 15 while (m--) { 16 int val; 17 scanf("%d", &val); 18 val %= q.size(); 19 20 for (int i = 0; i < val; i++) { 21 q.push(q.front()); 22 q.pop(); 23 } 24 25 printf("%d ", q.front()); 26 q.pop(); 27 } 28 29 return 0; 30 }
如果的数据范围很大,就需要用递推的做法了。
圆圈中最后剩下的数字
这 个数字 排成一个圆圈,从数字 开始每次从这个圆圈里删除第 个数字。
求出这个圆圈里剩下的最后一个数字。
数据范围
样例
输入:n=5 , m=3 输出:3
解题思路
这题的数据范围用模拟也可以过,时间复杂度为。但下面讲递推的做法,时间复杂度为。
我们假设在这个人中删除个人后最终留下的人为,这个人在长度为的序列中的下标就是。一开始在长度为的序列中删除第个人,如下图:
接下来进行第二次的删除操作,从被删除的下一个人开始,也就是下标为开始继续数个人然后删除。此时序列的长度变成了,我们为这个环剩余的数字重新编号,使得开始数的那个数的下标为,也就是这个数的下标为,顺时针往后的每个数下标都加,直到这个数时,下标为,如下图(新的下标的颜色为红色):
可以发现新的下标编号加上再模就是将原来的下标编号。原来在长度为的序列中,所在的下标重新编号后变成了,即。位置和位置上的数是同一个(就是最终留下来的那个数),只是下标的编号不一样。
所以我们发现,如果求出了在长度为的序列中从第个人开始,每次数个人将其删掉,最终留下的那个人的下标编号(在长度为的序列中的下标),那么就可以反推回这个最终留下的人在长度为的序列中的下标(加模)。
我们用来表示在个人中,每次从被删掉的下一个人开始数个人将其删除(一开始从开始数),最终留下的那个人的编号。
第一次删掉一个人后,序列长度变成了,并且重新进行编号,因此在长度为的序列中,最后留下的人的编号就是。
当我们得到新编号后,会发现与旧编号有个映射关系,也就是新的编号加上模后,就得到旧编号。
现在留下的人在的序列中的新编号是,因此转换为在的序列中的旧编号就是。
因此递推公式为(可以省略):
所以我们找到了和的递推关系,其中边界是只有一个人,此时编号为,即。
我们可以用递归或循环递推的写法来写。
当数据规模不大的时候可以用递归,AC代码如下:
1 class Solution { 2 public: 3 int lastRemaining(int n, int m){ 4 if (n == 1) return 0; 5 return (lastRemaining(n - 1, m) + m) % n; 6 } 7 };
循环递推写法的AC代码如下:
1 class Solution { 2 public: 3 int lastRemaining(int n, int m){ 4 int ret = 0; // f(1) = 0 5 for (int i = 2; i <= n; i++) { // 从2开始,一直求到n 6 ret = (ret + m) % i; // f(i) = (f(i-1) + m) % i 7 } 8 return ret; 9 } 10 };
下面是一道变形题目。
招聘
某公司招聘,有 个人入围,HR在黑板上依次写下 个正整数 ,然后这 个人围成一个圈,并按照顺时针顺序为他们编号 。
录取规则是:
第一轮从 号的人开始,取用黑板上的第 个数字,也就是 。
黑板上的数字按次序循环使用,即如果某轮用了第 个,如果 ,则下一轮需要用第 个;如果 ,则下一轮用第 个。
每一轮按照黑板上的次序取用到一个数字 ,淘汰掉从当前轮到的人开始按照顺时针顺序数到的第 个人。
下一轮开始时轮到的人即为被淘汰掉的人的顺时针顺序下一个人,被淘汰的人直接回家,所以不会被后续轮次计数时数到。
经过 轮后,剩下的最后 人被录取,所以最后被录取的人的编号与 相关。
输入格式
输入包含多组测试数据。
第一行包含整数 ,表示共有 组测试数据。
接下来 行,每行包含若干个整数,依次存放 ,表示一组数据。
输出格式
输出共 行,每行对应相应的那组数据确定的录取之人的编号。
数据范围
,
,
输入样例:
1 4 2 3 1
输出样例:
1
样例解释
样例里只有 组测试数据,说的是有 人入围(编号 )。
黑板上依次写下 个数字:、,那么:
第一轮:当前轮到 号,数到数字 ,顺时针数第 个人是 号,所以淘汰 号,下一轮从 号开始,目前剩余:、、;
第二轮:当前数到 号,取到数字 ,顺时针数第 个人是 号,所以淘汰 号,下一轮从 号开始,目前剩余:、;
第三轮:当前轮到 号,循环取到数字 ,顺时针数第 个人是 号,所以淘汰 号,最后只剩下 号,所以录取 号,输出 ;
解题思路
可以发现在这一题中,每次数的人的个数不再是一个固定的数了。但递推公式还是成立的,只需要把改为相应的从变到要数的那个数就可以了。
表示,如果要从长度为的序列中删除一个数,那么应该数第个数,即,因为数组的数是循环取的,因此要模上数组的长度。
递推公式就变成了
由于最大取到,递归来做肯定会暴栈的,因此必须要用循环。
AC代码如下:
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 5 const int N = 1010; 6 7 int a[N]; 8 9 int main() { 10 int tot; 11 scanf("%d", &tot); 12 while (tot--) { 13 int n, m; 14 scanf("%d %d", &n, &m); 15 for (int i = 0; i < m; i++) { 16 scanf("%d", a + i); 17 } 18 19 int ret = 0; // f(1) = 0 20 for (int i = 2; i <= n; i++) { 21 ret = (ret + a[(n - i) % m]) % i; // f(i) = (f(i-1) + a[(n-1) % m]) % i 22 } 23 printf("%d\n", ret); 24 } 25 26 return 0; 27 }
参考资料
AcWing 4400. 玩游戏(AcWing杯 - 周赛):https://www.acwing.com/video/3826/
AcWing 82. 圆圈中最后剩下的数字:https://www.acwing.com/video/205/
AcWing 1455. 招聘:https://www.acwing.com/solution/content/18760/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16159114.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效