题解:学而思编程 约瑟夫游戏(三)

【题目来源】

学而思编程:约瑟夫游戏(三)

【题目描述】

\(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

【核心思想】

  1. 问题分析\(n\) 个小朋友围成一圈,进行 \(k\) 轮淘汰。每轮从当前领头人的下一位开始顺时针数 \(a_i\) 个人,最后数到的人淘汰,其下一位成为新领头人。求每轮淘汰者的编号。这是一个队列模拟 + 取模优化问题,关键在于用队列维护当前存活者顺序,并通过取模避免无效循环。

  2. 算法选择

    • 队列模拟:用队列存储当前存活的小朋友编号,队首即为当前领头人
    • 取模优化\(a_i\) 可能很大,对当前存活人数取模后只需移动少量元素
    • 循环移位:将队首 \(x\) 个元素依次移到队尾,模拟顺时针数数过程
  3. 关键步骤

    • 初始化
      • 读取 \(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():将淘汰者移除队列
    • 队首自动更新为新领头人,进入下一轮
  4. 时间/空间复杂度

    • 时间复杂度:\(O(k \times \max(a_i \bmod \text{当前人数}))\),取模后每轮最多移动 \(O(n)\) 次,总复杂度 \(O(k \times n)\)
    • 空间复杂度:\(O(n)\),队列最多存储 \(n\) 个元素
  5. 队列模拟的核心思想

    • 循环结构映射:将环形结构映射为队列的循环移位,队首代表当前位置,队尾代表"绕回"的位置,天然模拟顺时针顺序
    • 取模降维\(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
posted @ 2026-07-04 13:32  团爸讲算法  阅读(10)  评论(0)    收藏  举报