题目描述
据说著名犹太历史学家 Josephus 有过以下故事:在罗马人占领乔塔帕特后,39 个犹太人与 Josephus 及他的朋友躲到一个洞中,39 个犹太人决定宁愿死也不要被敌人抓到,于是决定了一种自杀方式,41 个人排成一个圆圈,由第 1 个人开始报数,报数到 3 的人就自杀,然后再由下一个人重新报 1,报数到 3 的人再自杀,这样依次下去,直到剩下最后一个人时,那个人可以自由选择自己的命运。这就是著名的约瑟夫问题。现在请用单向环形链表得出最终存活的人的编号。
输入描述:
一行两个整数 n,m,n 表示链表的长度,m 表示每报数到 m 就自杀。
输出描述:
输出最后存活的人的编号(编号从 1 开始到 n)。
示例1
输入
5 2
输出
3
备注:
1 ≤ n , m ≤ 5 ∗ 1 0 6 1 \leq n,m \leq 5*10^6 1≤n,m≤5∗106
题解:
观察一下数据,在前面一篇 环形链表的约瑟夫问题 中,数据范围较小,可以以 O ( n ∗ m ) O(n*m) O(n∗m) 的时间复杂度进行模拟,但是本题的数据范围意味着我们需要考虑 O ( n ) O(n) O(n)时间复杂度的算法。之前的模拟是因为我们不知道到底哪个会活下来,所以通过不停的删除来淘汰无关节点,直到剩下一个节点。如果我们可以不用一直删除的方式,而是直接通过 n 和 m 计算哪个节点会活下来,然后找到该节点的编号即可。那么怎么找到呢?考虑如下问题:
1个人:fun(1) = 1;
2个人:fun(2);
…
n-1个人:fun(n-1);
n个人:fun(n)。
我们已经知道 fun(1) = 1 ,如果能确定 fun(i-1) 和 fun(i) 之间的关系,就可以通过递归求出 fun(n) 了。
假设 n 和 m 分别为 5 和 3,假设编号为 A 的人报的是 B ,则 A 和 B 的对应关系如下:
A | B |
---|---|
1 | 1 |
2 | 2 |
3 | 3 |
4 | 1 |
5 | 2 |
上面假设了 n 和 m 分别是 3 和 5 ,第一个被杀掉人的编号为 3 ,在杀掉 3 后,对剩下的人重新编号,如下:
A_old | A_new |
---|---|
1 | 3 |
2 | 4 |
3 | x |
4 | 1 |
5 | 2 |
x 表示人已经被杀掉,从上表中可以看到,编号之间的关系为:old = (new + 3 - 1) % 5 + 1,改写一下公式就是:old = (new + m - 1) % i + 1,i 表示有 i 个人。至此,我们找到了 fun(i-1)->new 和 fun(i)->old 之间的关系。
那么剩下的就是写代码了,由于每个状态只和前一个状态有关,可以通过循环实现,不需要用递归,见代码:
代码:
#include <cstdio>
using namespace std;
int main(void) {
int n, m;
scanf("%d%d", &n, &m);
int now = 0;
for (int i = 2; i <= n; ++i) now = (now + m) % i;
printf("%d\n", now + 1);
return 0;
}