当前位置: 首页 > 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.dtcms.com/a/311144.html

相关文章:

  • cs336之注意pytorch的tensor在哪里?(assert的使用)
  • vue3渲染html数据并实现文本修改
  • 【7.5 Unity AssetPostprocessor】
  • 大模型 + 垂直场景:搜索 / 推荐 / 营销 / 客服领域开发有哪些新玩法?
  • Flask 框架全面详解
  • C语言字符函数和字符串函数全解析:从使用到模拟实现
  • MyBatis与MySQL
  • 【安装教程】Docker Desktop 安装与使用教程
  • 从毫秒到真义:构建工业级RAG系统的向量检索优化指南
  • Python爬虫实战:研究mahotas库,构建图像获取及处理系统
  • (思维)洛谷 P13551 ももいろの鍵 题解
  • 位菜:仪式锚与价值符
  • 24黑马SpringCloud的Docker本地目录挂载出现相关问题解决
  • 【图像处理基石】用Python实现基础滤镜效果
  • LLM中Function Call的原理及应用
  • 工业仪表盘识别误检率↓79%!陌讯多模态融合算法在设备巡检中的落地优化​
  • 安全光幕Muting功能程序逻辑设计
  • [mssql] 分析SQL Server中执行效率较低的SQL语句
  • Git、Gitee、GitHub、GitLab完整讲解:从基础到进阶
  • GaussDB 约束的使用举例
  • Leetcode-141.环形链表
  • Rust: 工具链版本更新
  • Linux网络-------4.传输层协议UDP/TCP-----原理
  • Ajax——异步前后端交互提升OA系统性能体验
  • python chardet文本编码识别代码
  • Java单元测试和设计模式
  • uniapp 富文本rich-text 文本首行缩进和图片居中
  • Day 29: 复习
  • 音视频学习(四十五):声音的产生
  • 数据集-目标检测系列- 地球仪 数据集 globe>> DataBall