洛谷 P1996 约瑟夫问题之题解
文章目录
- 题目链接
- 洛谷P1996 约瑟夫问题 题面
- 题目描述
- 输入格式
- 输出格式
- 样例1
- 输入
- 输出 #1
- 说明 / 提示
- 注意:本题多解!
- 解法 1:STL list
- 解法 2:动态链表
- 解法 3:静态链表
- 1. 用结构体数组实现静态链表
- 2. 用结构体数组实现双向静态链表
- 3. 用一维数组实现单向静态链表
- 附:一组洛谷的评测数据
- 输入
- 输出
题目链接
https://www.luogu.com.cn/problem/P1996
洛谷P1996 约瑟夫问题 题面
题目描述
nnn 个人围成一圈,从第一个人开始报数,数到 mmm 的人出列,再由下一个人重新从 111 开始报数,数到 mmm 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。
注意:本题和《深入浅出-基础篇》上例题的表述稍有不同。书上表述是给出淘汰 n−1n-1n−1 名小朋友,而该题是全部出圈。
输入格式
输入两个整数 n,mn,mn,m。
输出格式
输出一行 nnn 个整数,按顺序输出每个出圈人的编号。
样例1
输入
10 3
输出 #1
3 6 9 2 7 1 8 5 10 4
说明 / 提示
1≤m,n≤1001 \le m, n \le 1001≤m,n≤100
注意:本题多解!
我们讲三个解法:
- STL list
- 动态链表
- 静态链表
解法 1:STL list
在算法竞赛或工程项目中常常使用 C++ 中的 STL list。list 是双向链表,由标准模板库(Standard Template Library, STL)管理,通过指针访问节点数据,高效率地插入和删除。
以下代码用 STL list 实现了这道题。
// STL list
#include <bits/stdc++.h>
using namespace std;int main() {int n, m;cin >> n >> m;list<int> node;for (int i = 1; i <= n; i++) node.push_back(i); // 建立链表list<int>::iterator it = node.begin();while (node.size() > 1) { // list 的大小由 STL 自己管理for (int i = 1; i < m; i++) { // 数到 mit++;if (it == node.end()) it = node.begin(); // 循环:到末尾后再回头}cout << *it << " ";list<int>::iterator next = ++it;if (next == node.end()) next = node.begin(); // 循环链表node.erase(--it); // 删除节点,node.size() 自动减 1it = next;}cout << *it;return 0;
}
解法 2:动态链表
动态链表需要临时分配链表节点,使用完毕后释放链表节点。动态链表的优点是能及时释放空间,不是用多余内存;缺点是需要管理空间,容易出错。动态链表是“教科书式”的标准做法。
以下代码用动态单向链表实现了这道题。
// 动态链表
#include <bits/stdc++.h>
using namespace std;
struct node {int data;node *next;
};
int main() {int n, m;scanf("%d%d", &n, &m);node *head, *p, *now, *prev;head = new node;head -> data = 1;head -> next = NULL;now = head;for (int i = 2; i <= n; i++) {p = new node;p -> data = i;p -> next = NULL;now -> next = p;now = p;}now -> next = head;prev = now, now = head;while ((n--) > 1) {for (int i = 1; i < m; i++) {prev = now;now = now -> next;}printf("%d ", now -> data);prev -> next = now -> next;delete now;now = prev -> next;}printf("%d", now -> data);delete now;return 0;
}
解法 3:静态链表
动态链表虽然“标准”,但是竞赛中一般不用。算法竞赛队内存管理要求不严格,为加快编码速度,一般就在题目允许的存储空间内静态分配,省去了动态分配和释放的麻烦。
静态链表使用预先分配的一段连续空间存储链表。从物理存储的意义上讲,“连续空间”并不符合链表的本义,因为链表的优点就是能克服连续存储的弊端。但是,用连续空间实现的链表,在逻辑上是成立的。具体有两种做法:①定义链表结构体数组,和动态链表的结构差不多;②使用一维数组,直接在数组上进行链表操作。
在此,我们会给出 333 段代码:用结构体数组实现单向静态链表、用结构体数组实现双向静态链表,用一维数组实现单向静态链表。
1. 用结构体数组实现静态链表
用结构体数组实现单向静态链表,注意静态分配应该定义在全局1,不能定义在函数内部。
// 结构体数组实现单向静态链表
#include <bits/stdc++.h>
const int N = 105; //定义静态链表的空间大小
struct node { //单向链表int id, nextid; //单向指针
// int data; //如有必要,定义一个有意义的数据
} nodes[N]; //定义在全局的静态分配
int main () {int n, m;scanf("%d%d", &n, &m); nodes[0].nextid = 1;for(int i = 1; i <= n; i++) {nodes[i].id = i;nodes[i].nextid = i + 1;}nodes[n].nextid = 1; //循环链表:尾指向头int now = 1, prev = 1; //从第 1 个节点开始while ((n--) > 1) {for(int i = 1; i < m; i++) {prev = now;now = nodes[now].nextid;} //数到 m 停下printf("%d ", nodes[now].id); //带空格打印 nodes[prev].nextid = nodes[now].nextid; //跳过 now 节点,即删除 nownow = nodes[prev].nextid; //新的 now}printf("%d", nodes[now].nextid); //打印最后一个节点,后面不带空格 return 0;
}
2. 用结构体数组实现双向静态链表
// 结构体数组实现双向静态链表
#include <bits/stdc++.h>
const int N = 105;
struct node { //双向链表int id; //节点编号//int data; //如有必要,定义一个有意义的数据int preid, nextid; //前一个节点,后一个节点
} nodes[N];
int main() {int n, m;scanf("%d%d", &n, &m);nodes[0].nextid = 1;for (int i = 1; i <= n; i++) { //建立链表nodes[i].id = i;nodes[i].preid = i - 1; //前节点nodes[i].nextid = i + 1; //后节点}nodes[n].nextid = 1; //循环链表:尾指向头nodes[1].preid = n; //循环链表:头指向尾int now = 1; //从第一个节点开始while ((n--) > 1) {for (int i = 1; i < m; i++) now = nodes[now].nextid; //数到 m 停下printf("%d ", nodes[now].id); //打印,后面带空格int prev = nodes[now].preid, next = nodes[now].nextid;nodes[prev].nextid = nodes[now].nextid; //删除 nownodes[next].preid = nodes[now].preid;now = next; //新的开始}printf("%d", nodes[now].nextid); //打印最后一个节点,后面不带空格return 0;
}
3. 用一维数组实现单向静态链表
这是最简单的实现方法。定义一个一位数组 nodes[]\text{nodes[\,]}nodes[],nodes[i]\text{nodes[}\,i\,\text{]}nodes[i] 的 iii 就是节点的值,而 nodes[i]\text{nodes[}\,i\,\text{]}nodes[i] 的值是下一个节点,它的使用环境很有限,因为他的节点只能存一个数据,就是 iii。
// 结构体数组实现双向静态链表
#include <bits/stdc++.h>
int nodes[150];
int main() {int n, m;scanf("%d%d", &n, &m);for (int i = 1; i <= n-1; i++) nodes[i] = i + 1; //nodes[i] 的值就是下一个节点nodes[n] = 1; //循环链表:尾指向头int now = 1, prev = 1; //从第一个节点开始while ((n--) > 1) {for (int i = 1; i < m; i++) {prev = now; now = nodes[now]; //数到 m 停下}printf("%d ", now); //打印,后面带空格nodes[prev] = nodes[now]; //跳过 now 节点,即删除 nownow = nodes[prev]; //新的开始}printf("%d", now); //打印最后一个节点,后面不带空格return 0;
}
附:一组洛谷的评测数据
输入
100 3
输出
3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60 63 66 69 72 75 78 81 84 87 90 93 96 99 2 7 11 16 20 25 29 34 38 43 47 52 56 61 65 70 74 79 83 88 92 97 1 8 14 22 28 35 41 49 55 62 68 76 82 89 95 4 13 23 32 44 53 64 73 85 94 5 19 37 50 67 80 98 17 40 59 86 10 46 77 26 71 31 100 58 91
P.S. 这组样例只是帮大家查错,尤其是灰名的,请勿用来打表!
大数组定义在全局的原因是有的评测环境的栈空间很小,大数组定义在局部占用了栈空间导致爆栈。现在各大在线判题系统(Online Judge, OJ)和比赛应该都会设置编译命令使栈空间等于内存大小,不会出现爆栈。虽然如此,在一些编译器中,大静态数组定义在函数内部会出错。 ↩︎