约瑟夫环
背景
讲的是罗马士兵包围了N个犹太人,犹太人不想被俘虏,所以决定围成一个圈,依次编号为1,2,3……N, 从1号开始,依次杀死左手边的人,最后一个人再自杀,但约瑟夫不想死,所以他要成为最后一个人,然后被俘虏。需要找出其编号(最后一个人)。
题目
背景故事太过于固定了,所以进行拓展一下:假设存在N个人,依次编号为1,2,3……N,从一号开始报数,当报道M号时,其被杀死,然后下一个人重新从1号开始报数,一样到M号被杀死……,最后剩下一个人,他的编号是多少?
分析
用数组来模拟这个问题(下标 = 编号 - 1),假设 N 人参与,剩下最后一人的下标为 \(f(N,M)\) ,那么可知 N - 1 人参与,剩下最后一人的下标是 \(f(N - 1,M)\) 。
-
假设已知 \(f(N,M)\) 的值,那么对于 \(f(N - 1,M)\) 值呢?
-
先杀死编号为M的人,然后从M+1号开始,那么剩下的人数为N-1,所以再将所有序号 - M,则编号有以下对应情况:
\[ …… M - 2 \rightarrow -2,M - 1 \rightarrow -1, M + 1 \rightarrow 1, M + 2 \rightarrow 2, M + 3 \rightarrow 3, …… \]对于越界情况:
\[ …… -3 \rightarrow N - 3, -2 \rightarrow N - 2, -1 \rightarrow N - 1, …… \]那么就和 N - 1人数模型一模一样了。
-
-
那么假设已知 \(f(N - 1,M)\) 的值,对于 \(f(N,M)\) 值呢?
- 这个就是列表 1 的逆推,先将编号向右添加M,即对应下标\(f(N - 1,M) + M\) ,为了防止越界,则进行取余处理,可以得到\(f(N,M) = (f(N - 1,M) + M)\) % \(N\),这就是递推公式了。
-
直接理解列表2,是有一定困难的,但是从列表 1 来理解列表 2,就非常容易了。对于列表 1,其越界情况表达式较复杂,而且是为列表 2 服务的,理解原理就行了,就不写出推导公式了。
代码
int JosephRing(const int& N,const int& M) {
/* 人数为 1 时,最后剩余人的编号-1 */
int ans = 0;
/* 这里的 i 是模型的人数n */
for(int i = 2;i <= N;i++) {
ans = (ans + M) % i;
}
/* 数组从0开始,加 1 ,跟编号对应 */
return ans + 1;
}