zoukankan      html  css  js  c++  java
  • 9.2练习题5 约瑟夫问题 题解

    题目出处:洛谷 P1996 ,略有修改。

    题目描述

    约瑟夫问题是一个非常经典的问题。
    n个人(n<=100)围成一圈,从第一个人开始报数,数到m的人出列,再由下一个人重新从1开始报数,数到m的人再出圈,……依次类推,直到所有的人都出圈,请输出依次出圈人的编号。

    输入格式

    输入一行包含两个正数 (n)(m) ,以一个空格分隔 (1 le n,m le 100)

    输出格式

    输出依次出圈人的编号,两两之间有一个空格分隔(最后没有空格,可参见样例)。

    样例输入

    10 3
    

    样例输出

    3 6 9 2 7 1 8 5 10 4
    

    题目分析如下

    约瑟夫环是一个比较经典的问题。还存在数学解法,如果感兴趣的话可以上百度搜索一下《约瑟夫环的数学解法》,会有很多博客讲解这个的(主要是汪老师懒得写囧~)。
    但是这里呢我们主要来分析这个问题的模拟解法。
    我们可以用很多我们学过的线性数据结构来模拟这个问题。
    我们可以用数组,可以用链表,也可以用队列。

    解法一:数组模拟
    这种方法是最原始最暴力的一种解法,首先,在知道 (n)(m) 的前提下,我们开一个数组 (out[])(out[i]) 用于表示第 (i) 个人是否已经离开了这个环, (out[i] = true) 表示这个人已经出去了; (out[i] = false) 表示这个人还在环里面。
    一开始所有的 (out[i]) 都为 false,因为一开始大家都在环里面。
    同时我开一个变量 (id) 用于表示当前报数的人的编号,一开始 (id = 1)
    然后我循环 (n) 轮(每一轮肯定会出环一个人),每一轮开始时我要初始化一个变量 (cnt = 0) ,用于表示当前已经报数的人的数量。
    然后接下来我就遍历 (id) ,如果 (out[i] == true) (说明第 (id) 个人当前没有出环),那么计数 (cnt++)
    如果此时 (cnt == m) ,说明已经报数累计了 (m) 人次,我只需要输出 (id) ,同时令 (out[id] = true) 即可(说明第 (id) 个人已经出去了),然后结束该轮。
    否则, (id ++) ,如果 (id > n) ,则令 (id = 1)(因为是环,所以第 (n) 个人报完数之后,又会回到第 (1) 个人继续报数)。
    实现代码如下:

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 110;
    bool out[maxn];
    int n, m;
    int main() {
        cin >> n >> m;
        int id = 1; // id表示当前报数的人的编号
        for (int i = 0; i < n; i ++) {  // 循环n轮,每次出去一个人
            int cnt = 0;    // 表示当前这轮报数的人的数量
            while (cnt < m) {
                if (!out[id]) {
                    cnt ++;
                    if (cnt == m) break;
                }
                id ++;
                if (id > n) id = 1;
            }
            if (i) putchar(' ');
            out[id] = true; // 第id个人出环
            cout << id;
        }
        cout << endl;
        return 0;
    }
    

    解法二:队列模拟
    因为大家可能都用过STL提供给我们的模拟队列的queue,所以这里再讲解一下使用queue来模拟约瑟夫环问题的方法。
    使用队列来模拟约瑟夫环问题的解题思路如下:
    首先将 1 到 n 这 n 个元素一次入队。
    接下来进行 n-1 轮,每轮先循环 m-1 次,每次将队首元素pop出来的同时在push进队尾,这 m-1次循环之后的队首元素就是报 m 个那个人的编号,输出队首元素,然后将队首元素pop出队列(这个元素就不用再push进队列了)。
    n-1 轮之后,队列中只剩下了一个人,就是我们的队首元素,我们只需要输出它,我们的1程序就可以结束了。
    实现代码如下:

    #include <bits/stdc++.h>
    using namespace std;
    int n, m;
    queue<int> que;
    int main() {
        cin >> n >> m;
        for (int i = 1; i <= n; i ++) que.push(i);
        for (int i = 1; i < n; i ++) {  // 循环n-1轮,每轮出去一个人
            for (int j = 0; j < m-1; j ++) {  // 前m-1个报数的人不需要出去
                que.push(que.front());
                que.pop();
            }
            cout << que.front() << " ";
            que.pop();  // 第m个报数的人出去
        }
        cout << que.front() << endl;    // 最后队列中只有一个人
        return 0;
    }
    

    不过这里大家要注意的是,老师的数据是没有问题的,但是洛谷原题的数据是有问题的,老师的这个程序在洛谷上面第3组测试样例没过,后来看了半天才发现原来第3组数据的输入是“0 0”,所以如果你要使用这个代码去提交到洛谷的话记得 (n = 0) 的时候要特殊判断。

  • 相关阅读:
    Linux里的2>&1究竟是什么
    表锁操作
    日志rsyslog
    计划任务at cron
    rpm包管理和源码包管理
    自建yum源(只演示nginx服务,其它都一样)
    软件包管理yum
    文件打包及压缩
    查找文件which locate find
    lsof恢复进程打开的文件
  • 原文地址:https://www.cnblogs.com/zifeiynoip/p/11524147.html
Copyright © 2011-2022 走看看