爱丽丝和鲍勃一起玩游戏,他们轮流行动。爱丽丝先手开局。
最初,黑板上有一个数字 n
。在每个玩家的回合,玩家需要执行以下操作:
- 选出任一
x
,满足0 < x < n
且n % x == 0
。 - 用
n - x
替换黑板上的数字n
。
如果玩家无法执行这些操作,就会输掉游戏。
只有在爱丽丝在游戏中取得胜利时才返回 true
。假设两个玩家都以最佳状态参与游戏。
示例 1:
输入:n = 2 输出:true 解释:爱丽丝选择 1,鲍勃无法进行操作。
示例 2:
输入:n = 3 输出:false 解释:爱丽丝选择 1,鲍勃也选择 1,然后爱丽丝无法进行操作
数学归纳法:
class Solution {
public:bool divisorGame(int n) {return n%2==0; }
};
数学归纳法的核心是:
- 基础步:验证
n
取最小的几个值时猜想成立; - 归纳步:假设
n ≤ k
时猜想成立,证明n = k+1
时猜想也成立。
1. 基础步:n=1 和 n=2 时结论成立
n=1
(奇数):1 没有小于自身的因数(无法操作),先手必败(符合猜想)。n=2
(偶数):2 的因数是 1,先手减去 1 后剩 1,后手无法操作,先手必胜(符合猜想)。
2. 归纳步:假设 n≤k 时成立,证明 n=k+1 时成立
分两种情况讨论 k
的奇偶性(因为 k
和 k+1
奇偶性相反):
情况 1:k 是偶数 → k+1 是奇数
n = k+1
是奇数,其所有因数x
必为奇数(奇数的因数都是奇数)。- 先手(Alice)必须减去一个奇数
x
,剩余数为(k+1) - x
:- 奇数减奇数 = 偶数,且
(k+1) - x ≤ k
(因为x ≥ 1
)。 - 根据归纳假设(
n ≤ k
时成立),偶数必为必胜态,此时轮到后手(Bob)面对偶数,Bob 必胜。
- 奇数减奇数 = 偶数,且
- 结论:
n=k+1
(奇数)时,Alice 必败(符合猜想)。
情况 2:k 是奇数 → k+1 是偶数
n = k+1
是偶数,其因数x
可以是奇数或偶数。- 先手(Alice)可以选择减去一个奇数
x
,剩余数为(k+1) - x
:- 偶数减奇数 = 奇数,且
(k+1) - x ≤ k
(因为x ≥ 1
)。 - 根据归纳假设(
n ≤ k
时成立),奇数必为必败态,此时轮到后手(Bob)面对奇数,Bob 必败。
- 偶数减奇数 = 奇数,且
- 结论:
n=k+1
(偶数)时,Alice 可以通过选择合适的x
让 Bob 必败,因此 Alice 必胜(符合猜想)。
最终结论
通过数学归纳法,证明了 “偶数时先手必胜,奇数时先手必败” 的猜想成立。
本质逻辑:奇数的因数都是奇数,操作后必然留下偶数(给对手必胜态);而偶数可以通过减去奇数留下奇数(给对手必败态),因此奇偶性直接决定了胜负。
动态规划:
class Solution {
public:bool divisorGame(int n) {// 创建一个布尔类型数组R,大小为n,用于记录每个数字的先手状态// R[i]表示:当数字为i时,当前玩家(先手)是否能获胜(true为胜,false为败)vector<bool> R(n, false);// 基础情况:// 当数字为1时,没有小于1的因数,先手无法操作,必败R[1] = false;// 当数字为2时,先手可以取因数1,剩余1给对手(对手必败),因此先手必胜R[2] = true;// 从数字3开始,依次推递推计算每个数字的胜负状态for(int i = 3; i < n + 1; i++) {// 枚举当前数字i的所有可能因数j(1 ≤ j < i)for(int j = 1; j < i; j++) {// 核心判断:// 1. j必须是i的因数(i % j == 0)// 2. 取走j后,剩余数字i-j对应的状态为必败(!R[i-j])// 如果存在这样的j,说明当前玩家可以通过取j让对手陷入必败态,因此当前玩家必胜if(i % j == 0 && !R[i - j]) {R[i] = true; // 标记当前数字i为必胜态break; // 找到一个有效j即可,无需继续枚举}}}// 返回数字n对应的先手状态(是否必胜)return R[n];}
};
通过定义状态 f[i]
记录 “当前数字为 i
时,先手是否必胜”,再从基础情况递推到更大的 i
。
1. 状态定义
f[i] = true
:当数字为i
时,先手必胜;f[i] = false
:当数字为i
时,先手必败。
2. 递推逻辑(核心)
对于数字 i
,先手的胜负取决于其所有可能的下一步操作:
- 先手可以选择
i
的任意一个因数j
(0 < j < i
),将数字变为i-j
(此时轮到后手操作,后手面对的状态是f[i-j]
); - 如果存在至少一个
j
,使得f[i-j] = false
(即后手面对i-j
时必败),那么先手可以选择这个j
,让后手陷入必败态,因此f[i] = true
(先手必胜); - 如果对于所有
j
,都有f[i-j] = true
(即后手面对所有可能的i-j
时都必胜),那么先手无论怎么操作都会让后手必胜,因此f[i] = false
(先手必败)。
3. 基础情况(边界条件)
f[0]
:无意义(无法操作);f[1]
:数字 1 没有小于自身的因数(j
不存在),先手无法操作,必败 →f[1] = false
;f[2]
:因数j=1
,操作后变为2-1=1
,此时后手面对f[1]=false
(必败),因此f[2] = true
(先手必胜)。
4. 递推过程(从前往后计算)
从 i=3
开始,依次计算 f[i]
:
- 枚举
i
的所有因数j
(0 < j < i
); - 对每个
j
,检查f[i-j]
是否为false
; - 只要存在一个
j
满足f[i-j] = false
,则f[i] = true
;否则f[i] = false
。
示例说明(以 i=3
为例)
i=3
的因数j
为1
(因为3
的因数是1
和3
,但j < 3
,所以只有j=1
);- 操作后数字变为
3-1=2
,此时后手面对f[2] = true
(后手必胜); - 由于所有可能的
j
对应的f[i-j]
都是true
,因此f[3] = false
(先手必败)。
本质逻辑
- 博弈问题的核心是 “让对手陷入必败态”;
- 动态规划通过记录每个状态的胜负,将大问题拆解为小问题(
i
的状态依赖于更小的i-j
的状态); - 从基础情况逐步递推,最终可得到任意
n
对应的f[n]
,即初始状态下先手的胜负。