[转载] 约瑟夫问题的数学方法(O(n))

tashj @ 2006-07-19 22:12

发信人 : goodhorsezxj (Eyes of Moon), 信区 : ACM
  : zz)[ 转载 ] 约瑟夫问题的数学方法 (O(n))
发信站 : 天大求实 BBS (Tue Jun 13 17:19:13 2006), 本站 (bbs.tju.edu.cn)

发信人 : kelefe ( 混沌中立 ), 信区 : ACMICPC
  : 【合集】 [ 转载 ] 约瑟夫问题的数学方法 (O(n))
发信站 : 逸仙时空 Yat-sen Channel (Tue Jun  6 16:40:57 2006), 站内信件

──── yanjunwei (Sat Jun  3 21:22:00 2006)   ───────────

以下文字转载自 yanjunwei 的信箱
原文由 yanjunwei 所发表

约瑟夫问题的数学方法
[ 2006-5-5 14:26:00 | By: lower ]
 
看到这个想起了去年的省赛上,我们就是被一个约瑟夫问题的变种搞的几乎发狂了,一直是 WA ,出了赛场才发现并
不是真正的约瑟夫问题。

对于约瑟夫问题,今天看到了一篇好帖子,是用数学方法处理的,感觉还不错的

无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂
度高达 O(nm) ,当 n m 非常大 ( 例如上百万,上千万 ) 的时候,几乎是没有办法在短时间内出结果的。

为了讨论方便,先把问题稍微改变一下,并不影响原意:

问题描述: n 个人(编号 0~(n-1)) ,从 0 开始报数,报到 (m-1) 的退出,剩下的人继续从 0 开始报数。求胜利者的编号


我们知道第一个人 ( 编号一定是 m%n-1) 出列之后,剩下的 n-1 个人组成了一个新的约瑟夫环(以编号为 k=m%n 的人开
始) :
  k  k+1  k+2  ... n-2, n-1, 0, 1, 2, ... k-2
并且从 k 开始报 0

现在我们把他们的编号做一下转换:
k     --> 0
k+1   --> 1
k+2   --> 2
...
...
k-2   --> n-2
k-1   --> n-1

变换后就完完全全成为了 (n-1) 个人报数的子问题,假如我们知道这个子问题的解:例如 x 是最终的胜利者,那么根
据上面这个表把这个 x 变回去不刚好就是 n 个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来: x'
=(x+k)%n

如何知道 (n-1) 个人报数的问题的解?对,只要知道 (n-2) 个人的解就行了。 (n-2) 个人的解呢?当然是先求 (n-3)
情况 ---- 这显然就是一个倒推问题!好了,思路出来了,下面写递推公式:

f[i] 表示 i 个人玩游戏报 m 退出最后胜利者的编号,最后的结果自然是 f[n]

递推公式
f[1]=0;
f[i]=(f[i-1]+m)%i;  (i>1)

有了这个公式,我们要做的就是从 1-n 顺序算出 f[i] 的数值,最后结果是 f[n] 。因为实际生活中编号总是从 1 开始,
我们输出 f[n]+1

由于是逐级递推,不需要保存每个 f[i] ,程序也是异常简单:

include <stdio.h>

main()
{
  int n, m, i, s=0;
  printf ("N M = "); scanf("%d%d", &n, &m);
  for (i=2; i<=n; i++) s=(s+m)%i;
  printf ("The winner is %d/n", s+1);
}

这个算法的时间复杂度为 O(n) ,相对于模拟算法已经有了很大的提高。算 n m 等于一百万,一千万的情况不是问题
了。
 
<think>好的,我现在需要帮助用户实现约瑟夫问题的数学解法,用C++编写。用户之前已经提供了数学解法的详细推导,现在需要具体的代码实现。 首先,我需要回顾约瑟夫问题的数学解法核心。根据之前的讨论,递推公式是f(n, m) = (f(n-1, m) + m) % n,其中f(1, m) = 0。这个公式可以通过迭代而不是递归来实现,这样时间复杂度是O(n),效率更高。 用户提供的引用中有几个C++的例子,比如引用3中的代码,它使用了循环来计算最终的存活者编号。不过用户可能希望代码更加清晰,并且有注释说明每一步的作用。同时,用户提到编号通常是从1开始的,而数学公式给出的是0-based的结果,所以需要调整输出,加1得到1-based的编号。 接下来,我需要检查用户提供的引用中的代码是否有错误或可以改进的地方。例如,引用3中的代码循环从i=2开始,到i<=n,每次更新idx = (idx + m) % i。这符合递推公式,是正确的。主函数输出idx +1,将结果转换为1-based编号。 用户可能希望代码包含详细的注释,解释数学解法的步骤,以及可能的测试示例。此外,还需要处理输入输出,让用户能够输入n和m的值,并显示结果。 另外,需要比较数学解法与模拟解法的区别,突出数学解法的高效性。例如,当n很大时,数学解法仍然可以在O(n)时间内解决,而模拟解法需要O(nm)时间,可能会很慢。 最后,确保代码的格式正确,符合C++标准,使用适当的头文件,比如#include <iostream>,using namespace std;,并处理用户输入的情况。 总结步骤: 1. 根据递推公式,使用循环实现数学解法。 2. 处理输入输出,让用户输入n和m。 3. 将0-based结果转换为1-based编号(如果需要)。 4. 添加注释说明代码逻辑。 5. 提供测试示例,验证代码正确性。 6. 比较不同解法的效率,说明数学解法的优势。</think>### C++ 约瑟夫问题数学解法实现 #### 数学解法核心公式 递推公式: $$ f(n,m) = (f(n-1,m) + m) \% n \quad \text{(0-based编号)} $$ #### 完整代码实现 ```cpp #include <iostream> using namespace std; /** * 约瑟夫问题数学解法(时间复杂度O(n)) * @param n 总人数(编号从1开始) * @param m 淘汰间隔(每数到m的人出局) * @return 最后存活者的原始编号(1-based) */ int josephusMath(int n, int m) { int res = 0; // f(1,m)=0(0-based基准值) // 递推计算f(2,m)到f(n,m) for (int i = 2; i <= n; ++i) { res = (res + m) % i; // 关键递推公式 } return res + 1; // 转换为1-based编号 } int main() { int n, m; cout << "输入总人数n: "; cin >> n; cout << "输入淘汰间隔m: "; cin >> m; cout << "最后存活者编号: " << josephusMath(n, m) << endl; // 验证示例(n=7, m=3 → 输出4) // cout << "验证示例(7,3): " << josephusMath(7, 3) << endl; return 0; } ``` --- #### 关键代码解析 1. **递推实现**: ```cpp for (int i = 2; i <= n; ++i) { res = (res + m) % i; } ``` - 从$f(1,m)=0$开始迭代计算 - 每轮计算$f(i,m) = (f(i-1,m) + m) \% i$ 2. **编号转换**: ```cpp return res + 1; // 0-based转1-based ``` - 数学推导使用0-based编号 - 实际应用通常需要1-based结果 --- #### 测试示例对比 | 输入(n,m) | 数学解法输出 | 实际存活编号 | 计算步骤验证 | |---------|------------|------------|-------------| | (7,3) | 4 | 正确[^3] | 见下方详细步骤 | | (41,2) | 19 | 正确[^1] | 使用特殊公式验证 | | (1,5) | 1 | 正确 | 边界条件验证 | **计算步骤示例(n=7, m=3)**: ``` i=2 → res=(0+3)%2=1 i=3 → (1+3)%3=1 i=4 → (1+3)%4=0 i=5 → (0+3)%5=3 i=6 → (3+3)%6=0 i=7 → (0+3)%7=3 → 最终结果3(0-based)→ 4(1-based) ``` --- #### 性能对比 | 方法 | 时间复杂度 | 适用场景 | |------------|------------|--------------------| | 数学解法 | O(n) | n≤1e8的规模[^2] | | 模拟队列 | O(nm) | 仅用于教学演示 | | 递归解法 | O(n) | 易栈溢出(n>1e4) | --- ### 常见问题解答 **Q1: 为什么代码中要使用`%i`而不是`%n`?** A1: 每次迭代对应i人存活的情况,取模操作保证索引始终在有效范围内[^4] **Q2: 如何处理m=0的特殊情况?** A2: 实际应用中m≥1,若需要处理m=0,可添加输入校验: ```cpp if (m < 1) { cerr << "m必须≥1" << endl; return -1; } ``` **Q3: 该算法是否支持大规模计算?** A3: 在n=1e8时仍能在1秒内完成(现代CPU),而模拟解法在n=1e5时就会超时 --- ### 进阶优化方向 1. **位运算加速**(仅适用于m=2的特殊情况): ```cpp int josephus2(int n) { return 2*(n - (1 << (31 - __builtin_clz(n)))) + 1; } ``` 2. **批量处理模运算**(当m远大于n时): ```cpp res = (res + m) % i; // 优化为: res += m; if (res >= i) res %= i; ``` --- ### 引用文献 [^1]: 约瑟夫问题递归公式推导 [^2]: 算法时间复杂度分析标准 [^3]: 递推法数学证明过程 [^4]: C++容器性能对比研究
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值