博弈论
总述
约瑟夫斯问题
n个人围成圈编号{0..n-1},从1号开始每次消去第m个人直到最后一个 人,计算最后人编号$J(n)$。
减1法
考虑每次消去1人后剩余人的编号情况
还剩k人时,消去1人后,以下个人编号为0开始,将剩余人重新 编号,得到每个人在剩k-1人时的编号
相较于剩k人时,剩k-1人时每个人编号都减少m,即每个人在剩 k人时编号满足
考虑只剩1人时,其编号为0,则可以递推求解
算法
1 | Joseph_1(n, m): |
特点
- 算法效率
- 时间效率$\in O(n)$
减常因子
剩余人数$k >= m$时考虑所有人都报数一次后剩余人的编号变化情况
还剩k人时,报数一圈后消去
k//m
人,以最后被消去人的下个人 编号为0开始,将剩余人重新编号,得到剩k-k/m
人时的编号相较于剩k人时,剩
k-k//m
人时每个人编号满足- $d = k // m$
- $s = J_{k - d} - n\%m$
$k < m$时,使用减1法计算
- m很大时,以$k < m$作为调用减1法很容易使得递归超出 最大值
- 另外$m < k <= d * m$时,每轮也不过消去$d$个人,而 递推式复杂许多、需要递归调用
- 所以具体代码实现中应该选择较大的$d$值,如5
算法
1 | Joseph_factor(n, m): |
特点
算法效率
- 时间效率$\in O(log n) + m$
特别的,对$m=2$时
- $n=2k$为偶数时,$J(2k)=2J(k)-1$
- $n=2k+1$为奇数时,$J(2k+1)=2J(k)+1$
任意第k个
考虑报数不重置,则第k个被消去的人报数为$k * m - 1$
对报数为$p = k * m + a, 0 \leq a < m$的人
此时已经有k个人被消去,剩余n-k个人
则经过$n - k$个剩余人报数之后,该人才继续报数,则 其下次报数为$q = p + n - k = n + k*(m-1) + a$
若该人报数$p$时未被消去,则$a \neq m-1$,则可以得到 $p = (q - n) // (m-1) * m + (q-n) \% (m-1)$
算法
1 | Joseph_k(n, m, k): |
算法特点
算法效率
- 时间效率$\in O(log n)$
特别的,m=2时对n做一次向左循环移位就是最后者编号
双人游戏
- 双人游戏中往往涉及两个概念
- state:状态,当前游戏状态、数据
- move:走子,游戏中所有可能发生的状态改变
- 状态、走子彼此之间相互“调用”
- 状态调用走子转化为下个状态
- 走子调用状态评价当前状态
1 | make_move(state, move): |
拈游戏
同样局面,每个玩家都有同样可选走法,每种步数有限的走法都能 形成游戏的一个较小实例,最后能移动的玩家就是胜者。
- 拈游戏(单堆版):只有一堆棋子n个,两个玩家轮流拿走最少 1个,最多m个棋子
- 拈游戏(多堆版):有I堆棋子,每堆棋子个数分别为 ${n_1,\cdots,n_I}$,可以从任意一堆棋子中拿走任意允许数量 棋子,甚至拿走全部一堆
减可变规模算法
算法
(单堆)从较小的n开始考虑胜负(标准流程)
- n=0:下个人失败
- 1<=n<=m:下个人胜利(可以拿走全部)
- n=m+1:下个人失败(无论拿走几个,对方符合1<=n<=m 胜利条件)
- 数学归纳法可以证明:n=k(m+1)时为败局,其余为胜局
1 | // 两个函数轮流递归调用 |
特点
堆为2时,需要对两堆是否相同分别考虑
对更一般的I堆时
- 二进制数位和(拈和):每位求和并忽略进位(奇或)