当前位置: 首页 > news >正文

除数博弈(动态规划)

爱丽丝和鲍勃一起玩游戏,他们轮流行动。爱丽丝先手开局。

最初,黑板上有一个数字 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;  }
};

数学归纳法的核心是:

  1. 基础步:验证 n 取最小的几个值时猜想成立;
  2. 归纳步:假设 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 的任意一个因数 j0 < 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 的所有因数 j0 < 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],即初始状态下先手的胜负。

 

 

 

http://www.lryc.cn/news/607805.html

相关文章:

  • [硬件电路-124]:模拟电路 - 信号处理电路 - 测量系统的前端电路详解
  • python匿名函数lambda
  • 【LeetCode刷题指南】--二叉树的前序遍历,二叉树的中序遍历
  • 2025熵密杯 -- 初始谜题 -- Reproducibility
  • 进阶向:自动化天气查询工具(API调用)
  • stm32是如何实现电源控制的?
  • 【7.5 Unity AssetPostprocessor】
  • 2-5 Dify案例实践—利用RAG技术构建企业私有知识库
  • 【最新区块链论文录用资讯】CCF A--WWW 2025 23篇
  • 第三章 用户和权限
  • 【C++】第二十一节—一文详解 | 红黑树实现(规则+效率+结构+插入+查找+验证)
  • 【RK3568 RTC 驱动开发详解】
  • 网安-中间件(updating..)
  • jenkins从入门到精通-P1—九五小庞
  • 【机器学习】非线性分类算法详解(下):决策树(最佳分裂特征选择的艺术)与支持向量机(最大间隔和核技巧)
  • Docker 的网络模式
  • OTC焊接机器人节能技巧
  • Python 第一阶段测试题 答案及解析
  • 机器学习【五】decision_making tree
  • 高性能MCP服务器架构设计:并发、缓存与监控
  • 淘宝小程序的坑
  • Clickhouse#表记录转换为insert语句
  • 【机器学习】“回归“算法模型的三个评估指标:MAE(衡量预测准确性)、MSE(放大大误差)、R²(说明模型解释能力)
  • Human Brain Mapping:静息态功能磁共振成像的回归动态因果建模
  • C语言(长期更新)第7讲:VS实用调试技巧
  • ADB 底层原理
  • Android 运行 deno 的新方法 (3): Termux 胖喵安初
  • 【Leetcode hot 100】49.字母异位词分组
  • [mssql] 分析SQL Server中执行效率较低的SQL语句
  • imx6ull-驱动开发篇6——Linux 设备树语法