约瑟夫环学习笔记
# 约瑟夫环
约瑟夫问题是个著名的问题:N个人围成一圈,第一个人从1开始报数,报M的将被杀掉,下一个人接着从1开始报。如此反复,最后剩下一个,求最后的胜利者。
当N,M较大时,直接模拟显然很蠢,因此需要一种更高效的方法!
# 公式法
我们给每一个人从到编号。问题转变成求最后一个胜利者的编号。
定义为:个人报数,报到的人被杀掉时,最终获胜者的编号。
接下来,我们思考一下与之间是否存在递推关系
假设得到的获胜者为个人中的第个(编号不一定为),那么他在原来的个人中应当是第个人(从0开始算)
因而有
def survivor(N: int, M: int):
survivor_idx = 0
for n in range(2, N + 1):
survivor_idx = (survivor_idx + M) % n
return survivor_idx
1
2
3
4
5
2
3
4
5
复杂度
# 约瑟夫环优化
# 例题: HDU 3089
约瑟夫环,求最后活下来的人的序号。
数据范围:
显然对于本题,上述算法不再适用。
可以观察到,本题的比较小,可以做一下优化:
记的转移方程有两种情况
对于第一种情况,显然可以将多次递推合并;第二种情况不优化,直接递推。
现在考虑的情况
在第一圈时,会筛掉个人,令,从第个人开始重编号,如下表所示:
A | B | C | D | E | F | G | H | |
---|---|---|---|---|---|---|---|---|
原编号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
重编号 | 2 | 3 | / | 4 | 5 | / | 0 | 1 |
易得尾部
共有个人
问题转化为的子问题,假设子问题的答案为,分两种情况讨论:
- :即最终获胜者在
尾部
,其对应的编号s - 若:即最终获胜者不在尾部,其对应的编号
当时,直接老方法递归即可。
切记最后再特判一下的情况。
时间复杂度
AC代码
#include <iostream>
#define ll long long
#define Android ios::sync_with_stdio(false), cin.tie(NULL)
using namespace std;
ll calc(ll n, ll m){
ll ans = 0;
for(int i=2; i<=n; i++){
ans = (ans + m) % i;
}
return ans;
}
ll solve(ll n, ll m){
if(m == 1) return n - 1;
if(n <= m) return calc(n, m);
ll k = n % m, s = n / m * m;
ll ans = solve(n - n / m, m);
if(ans < k) return ans + s;
return ans - k + ((ans - k) / (m - 1));
}
signed main(){
Android;
ll n, m;
while(cin >> n >> m){
cout << solve(n, m) + 1 << '\n';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 参考链接
https://blog.csdn.net/u011500062/article/details/72855826
https://www.zhihu.com/question/273665007
上次更新: 2021/02/24, 03:37:30