题解:学而思编程 约瑟夫游戏(三)
【题目来源】
学而思编程:约瑟夫游戏(三)
【题目描述】
有 \(n\) 个小朋友围成一圈,玩数数游戏。小朋友们按顺时针顺序,依次编号为 \(1\sim n\)。
初始时,\(1\) 号小朋友被指定为领头人。游戏一共会进行 \(k\) 轮。在第 \(i\) 轮中,领头人会从他的顺时针方向的下一个人开始,按顺时针顺序数 \(a_i\) 个人。其中,最后一个被领头人数到的人被淘汰出局,这也意味着该轮游戏结束。出局者的顺时针方向的下一个人被指定为新领头人,引领新一轮游戏。
例如,假设当游戏即将开始第i轮时,还剩下 \(5\) 个小朋友,编号按顺时针顺序依次为 \(8,10,13,14,16\),并且当前领头人为 \(13\) 号小朋友,\(a_i=12\),则第 \(i\) 轮游戏结束后,最后一个被数到的小朋友为 \(16\) 号小朋友,他将被淘汰出局,并且处于其下一位的第 \(8\) 号小朋友将被指定为新领头人。
现在,请你求出每一轮被淘汰的小朋友的编号。
【输入】
第一行两个整数 \(n\),\(k\);
第二行 \(k\) 个整数 \(a_1,a_2,\dots,a_n\)。
【输出】
一行 \(k\) 个整数,其中第 \(i\) 个整数表示在第 \(i\) 轮中被淘汰的小朋友编号。
【输入样例】
7 5
10 4 11 4 1
【输出样例】
4 2 5 6 1
【核心思想】
-
问题分析:\(n\) 个小朋友围成一圈,进行 \(k\) 轮淘汰。每轮从当前领头人的下一位开始顺时针数 \(a_i\) 个人,最后数到的人淘汰,其下一位成为新领头人。求每轮淘汰者的编号。这是一个队列模拟 + 取模优化问题,关键在于用队列维护当前存活者顺序,并通过取模避免无效循环。
-
算法选择:
- 队列模拟:用队列存储当前存活的小朋友编号,队首即为当前领头人
- 取模优化:\(a_i\) 可能很大,对当前存活人数取模后只需移动少量元素
- 循环移位:将队首 \(x\) 个元素依次移到队尾,模拟顺时针数数过程
-
关键步骤:
- 初始化:
- 读取 \(n\)(总人数)和 \(k\)(轮数)
- 创建队列,将 \(1\) 到 \(n\) 依次入队
- 执行 \(k\) 轮淘汰:
- 读取本轮报数 \(a_i\)
x = a_i \bmod q.size():对当前存活人数取模,得到实际需要移动的位置数- 循环 \(x\) 次:
q.push(q.front()):将队首元素移到队尾q.pop():移除原队首
- 此时队首即为要数到的第 \(a_i\) 个人(淘汰者)
- 输出
q.front() q.pop():将淘汰者移除队列
- 队首自动更新为新领头人,进入下一轮
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(k \times \max(a_i \bmod \text{当前人数}))\),取模后每轮最多移动 \(O(n)\) 次,总复杂度 \(O(k \times n)\)
- 空间复杂度:\(O(n)\),队列最多存储 \(n\) 个元素
-
队列模拟的核心思想:
- 循环结构映射:将环形结构映射为队列的循环移位,队首代表当前位置,队尾代表"绕回"的位置,天然模拟顺时针顺序
- 取模降维:\(a_i\) 可能远大于当前存活人数,利用
a_i % q.size()将移动次数压缩到 \([0, \text{size}-1]\),避免 \(O(a_i)\) 的无效操作 - 领头人自动维护:淘汰者出队后,原队首(淘汰者的下一位)自动成为新领头人,无需额外标记
- 约瑟夫问题的队列解法:经典约瑟夫问题的变体,队列的 FIFO 特性完美契合"顺时针顺序数数"的语义
- 适用于"环形淘汰 + 顺序报数"类问题,队列配合取模可将大规模报数优化到线性规模
【算法标签】
队列
【代码详解】
#include<bits/stdc++.h>
using namespace std;
int n, k, a[105], now, cnt; // n: 总人数,k: 出局人数,a: 报数序列,now: 当前位置,cnt: 计数
bool b[105]; // 标记是否已出局
int main()
{
cin >> n >> k; // 输入总人数n和出局人数k
for (int i = 1; i <= k; i++) // 输入报数序列
{
cin >> a[i];
}
for (int i = 1; i <= k; i++) // 进行k轮出局
{
cnt = 0; // 计数清零
int m = a[i] % (n - i + 1); // 计算实际需要数的人数(考虑循环)
while (cnt <= m) // 数m+1个人(包括起点)
{
now = (now + 1 + n - 1) % n + 1; // 移动到下一个位置,循环处理
if (b[now] == 0) // 如果当前位置的人未出局
cnt++; // 计数加1
}
b[now] = 1; // 标记当前位置的人出局
cout << now << " "; // 输出出局的人的编号
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
int main()
{
queue<int> q; // 队列存储人员编号
int n, k;
cin >> n >> k; // 输入总人数n和操作次数k
// 初始化队列,人员编号1~n
for (int i = 1; i <= n; i++)
{
q.push(i);
}
while (k--) // 执行k次操作
{
int x;
cin >> x; // 输入本轮要移动的位置数
// 取模,避免重复移动
x = x % q.size();
// 将前x个人移到队尾
for (int i = 0; i < x; i++)
{
q.push(q.front()); // 队首元素移到队尾
q.pop(); // 移除队首
}
// 输出当前队首(要淘汰的人)
cout << q.front() << " ";
// 淘汰这个人
q.pop();
}
return 0;
}
【运行结果】
7 5
10 4 11 4 1
4 2 5 6 1
浙公网安备 33010602011771号