当前位置: 首页 > 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.xdnf.cn/news/1229977.html

相关文章:

  • wxPython 实践(六)对话框
  • 【05】OpenCV C#——OpenCvSharp 图像基本操作---转灰度图、边缘提取、兴趣区域ROI,图像叠加
  • Day25-对称二叉树-
  • react 和 react native 的开发过程区别
  • React ahooks——副作用类hooks之useThrottleEffect
  • 再见!三层框架开发
  • Java中的sort()排序详解
  • 涉水救援机器人cad【12张】三维图+设计书明说
  • linux编译基础知识-头文件标准路径
  • 轻量级鼠标右键增强工具 MousePlus
  • eSIM技术深度解析:从物理芯片到数字革命
  • Python从入门到精通——第五章 列表与元组
  • 机器学习【五】decision_making tree
  • 深入解析Java Stream Sink接口
  • JVM学习日记(十四)Day14——性能监控与调优(一)
  • 小迪23年-22~27——php简单回顾(2)
  • IMAP电子邮件归档系统Mail-Archiver
  • rabbitmq消息队列详述
  • 【Android】使用 Intent 传递对象的两种序列化方式
  • 深度学习-模型初始化与模型构造
  • 高性能MCP服务器架构设计:并发、缓存与监控
  • 【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 微博舆情数据可视化分析-热词情感趋势树形图
  • 【机器学习】非线性分类算法详解(下):决策树(最佳分裂特征选择的艺术)与支持向量机(最大间隔和核技巧)
  • 在 AKS 中运行 Azure DevOps 私有代理-1
  • Linux性能监控与调优全攻略
  • React ahooks——副作用类hooks之useThrottleFn
  • React ahooks——副作用类hooks之useDebounceFn
  • Shell【脚本 02】离线安装配置Zookeeper及Kafka并添加service服务和开机启动(脚本分析)
  • 堆----1.数组中的第K个最大元素
  • 通过filezilla在局域网下实现高速传输数据