top1编程
← 返回题目
题解

选标兵

1 条题解

  • 0
    @ 2026-8-7 12:55:51

    P4839 选标兵(基础)

    解题思路

    第一步,理解报数出列的规则。 有 M 个人,编号分别是1到M,他们围成一圈站好。从编号1的人开始,顺时针依次报数1、2、3……报到数字 N 的那个人出列;然后从出列那个人的下一个人开始,又从1重新报数。这样一直循环,直到所有人都出列为止。题目要我们按出列的先后顺序,把编号一行一行输出来。

    第二步,用数组模拟圆圈。 围成一圈站人,可以用数组 a 表示,a[i]=1 表示编号 i 的人还在圈里,a[i]=0 表示他已经出列了。用变量 pos 记录"目前数到哪个人",顺时针移动的时候 pos 加1,如果 pos 超过了 M,就把它变成1,这样队伍就首尾相接成了一圈。

    第三步,数到 N 的人出列。 每次从当前 pos 的位置开始数数:让 pos 一步步往前移动,但移动的时候只看还在圈里的人——只有 a[pos] 等于1才算是数了一步,等于0(已经出列)就直接跳过。数满 N 步之后停下来,停在的那个人就是要出列的,把 a[pos] 改成0、人数 left 减1,并输出 pos。然后开始下一轮报数,此时 pos 正好停在这个出列的人身上,下一轮第一下就会走到他的下一个人,完全符合"从下一个人重新报数"。

    第四步,注意数据范围。 M 在8到15之间,人数很少;但 N 可能很大,最大到32767。数 N 步其实就是在最多15个人之间转圈,最多转三千多圈,每圈15步,总共不到50万次操作,程序跑得非常快。

    第五步,拿样例验证。 M=9、N=6:从1开始数,1、2、3、4、5、6,6出列;从7开始数,7、8、9、1、2、3,3出列;从4开始数,4、5、7、8、9、1,1出列……一直数下去,出列顺序是 6 3 1 9 2 5 4 8 7,和样例一致。

    参考代码

    // 选标兵:约瑟夫问题,M人围成一圈报数,数到N出列,输出全部出列顺序
    #include <iostream>
    using namespace std;
    
    int main() {
        int a[16];   // a[i]=1表示第i个人还在圈里
        int m, n, i;
        cin >> m >> n;
        for (i = 1; i <= m; i++) a[i] = 1;
        int left = m, pos = 0;
        while (left > 0) {
            int step = n;   // 报数报到n的人出列
            while (step > 0) {
                pos++;               // 顺时针走到下一个人
                if (pos > m) pos = 1;
                if (a[pos]) step--;  // 只数还在圈里的人
            }
            a[pos] = 0;              // 该人出列
            left--;
            cout << pos << endl;
        }
        return 0;
    }
    

    复杂度分析

    M 最大为15,每个人出列时都要数 N 步,N 最大为32767,所以总操作次数大约是 M×N,最多不到50万次,时间复杂度 O(M×N),运行速度很快。空间上只需要一个长度为16的数组记录每个人是否还在圈里,空间复杂度是 O(M)。

    • 1