Chgtaxihe's blog Chgtaxihe's blog
首页
练习
算法学习
读书笔记
小玩意

Chgtaxihe

首页
练习
算法学习
读书笔记
小玩意
  • 数论

  • 动态规划

  • 暴力

  • 图论

  • 数据结构

  • 字符串

  • 几何

  • 博弈论

  • 其他

    • 01分数规划学习笔记
    • CDQ分治学习笔记
    • wqs二分学习笔记
    • 一两句话小技巧
    • 单调性应用学习笔记
    • 哈希函数(Hash)函数的设计
    • 带权并查集处理线段
    • 环检测算法学习笔记
    • 约瑟夫环学习笔记
      • 公式法
      • 约瑟夫环优化
        • 例题: HDU 3089
    • 组合情况
    • 高效位运算

约瑟夫环学习笔记

# 约瑟夫环

约瑟夫问题是个著名的问题:N个人围成一圈,第一个人从1开始报数,报M的将被杀掉,下一个人接着从1开始报。如此反复,最后剩下一个,求最后的胜利者。

当N,M较大时,直接模拟显然很蠢,因此需要一种更高效的方法!

# 公式法

我们给每一个人从000到N−1N-1N−1编号。问题转变成求最后一个胜利者的编号。

定义f(N,M)f(N, M)f(N,M)为:NNN个人报数,报到MMM的人被杀掉时,最终获胜者的编号。

接下来,我们思考一下f(N,M)f(N,M)f(N,M)与f(N−1,M)f(N-1,M)f(N−1,M)之间是否存在递推关系

假设f(N−1,M)f(N-1,M)f(N−1,M)得到的获胜者为N−1N-1N−1个人中的第xxx个(编号不一定为xxx),那么他在原来的NNN个人中应当是第(M+x)%N(M + x) \% N(M+x)%N个人(从0开始算)

因而有

f(N,M)=(f(N−1,M)+M)modNf(N,M)=(f(N-1,M) + M) \mod N f(N,M)=(f(N−1,M)+M)modN

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

复杂度O(n)O(n)O(n)

# 约瑟夫环优化

# 例题: HDU 3089

约瑟夫环,求最后活下来的人的序号。

数据范围: N≤1012,M≤1000N\le10^{12},M\le 1000N≤1012,M≤1000

显然对于本题,上述算法不再适用。

可以观察到,本题的MMM比较小,可以做一下优化:

记f(n,M)f(n,M)f(n,M)的转移方程有两种情况

X=f(n−1,M)+MF(n,M)={X,X<nX%n,X≥nX=f(n-1, M)+M\\F(n, M)=\left\{\begin{aligned}X, X<n \\X\%n , X\ge n\end{aligned}\right. X=f(n−1,M)+MF(n,M)={X,X<nX%n,X≥n​

对于第一种情况,显然可以将多次递推合并;第二种情况不优化,直接递推。

现在考虑N=8,M=3N=8,M=3N=8,M=3的情况

在第一圈时,会筛掉⌊83⌋=2\lfloor\frac{8}{3}\rfloor=2⌊38​⌋=2个人,令s=M∗⌊83⌋s=M*\lfloor{\frac{8}{3}}\rfloors=M∗⌊38​⌋,从第s+1s+1s+1个人开始重编号,如下表所示:

A B C D E F G H
原编号 0 1 2 3 4 5 6 7
重编号 2 3 / 4 5 / 0 1

易得尾部共有k=n−s+1=n%M=2k=n - s + 1=n\%M=2k=n−s+1=n%M=2个人

问题转化为n′=6n'=6n′=6的子问题,假设子问题的答案为ans′ans'ans′,分两种情况讨论:

  1. ans′<kans' < kans′<k:即最终获胜者在尾部,其对应的编号ans=ans′+ans=ans'+ans=ans′+s
  2. 若ans′≥kans'\ge kans′≥k:即最终获胜者不在尾部,其对应的编号ans=ans′−k+⌊ans′−kM−1⌋ans=ans'-k+\lfloor\frac{ans'-k}{M-1}\rfloorans=ans′−k+⌊M−1ans′−k​⌋

当n≤kn\le kn≤k时,直接老方法递归即可。

切记最后再特判一下M=1M=1M=1的情况。

时间复杂度O(MlogN)O(MlogN)O(MlogN)

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

# 参考链接

https://blog.csdn.net/u011500062/article/details/72855826

https://www.zhihu.com/question/273665007

上次更新: 2021/02/24, 03:37:30
环检测算法学习笔记
组合情况

← 环检测算法学习笔记 组合情况→

最近更新
01
深入理解Java虚拟机
03-04
02
DNS工作原理
02-27
03
改用VuePress啦
02-23
更多文章>
Theme by Vdoing | Copyright © 2019-2021
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式