【C++前缀和 动态规划 博弈】1140. 石子游戏 II|2034

ops/2024/9/30 2:07:27/

本文涉及的基础知识点

C++算法前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频
C++动态规划
博弈:往往后续状态已知,前续状态未知

LeetCode1140. 石子游戏 II

Alice 和 Bob 继续他们的石子游戏。许多堆石子 排成一行,每堆都有正整数颗石子 piles[i]。游戏以谁手中的石子最多来决出胜负。
Alice 和 Bob 轮流进行,Alice 先开始。最初,M = 1。
在每个玩家的回合中,该玩家可以拿走剩下的 前 X 堆的所有石子,其中 1 <= X <= 2M。然后,令 M = max(M, X)。
游戏一直持续到所有石子都被拿走。
假设 Alice 和 Bob 都发挥出最佳水平,返回 Alice 可以得到的最大数量的石头。
示例 1:
输入:piles = [2,7,9,4,4]
输出:10
解释:如果一开始 Alice 取了一堆,Bob 取了两堆,然后 Alice 再取两堆。Alice 可以得到 2 + 4 + 4 = 10 堆。
如果 Alice 一开始拿走了两堆,那么 Bob 可以拿走剩下的三堆。在这种情况下,Alice 得到 2 + 7 = 9 堆。返回 10,因为它更大。
示例 2:
输入:piles = [1,2,3,4,5,100]
输出:104
提示:
1 <= piles.length <= 100
1 <= piles[i] <= 104

错误解法:动态规划+前缀和

N = piles ,2M大于n没意义,所以本题假定其小于等于n。

动态规划的状态表示

m为当前可以移除的石子数量,i为已经移除石头堆数。
dp0[i][m] 表示,当前行动者是Alice,Alice最小分数。
dp1[i][m] 表示 当前行动者是Bom,Alice的最大分数。
空间复杂度:O(nM)

动态规划的转移方程

枚举前置状态dp0[i][m],令 Alice移除了x块石头,1 <= x ,i+x <= N
m1 = max(m,2x)
MaxSelf(dp1[i+x][m1],preSum[i+x]- dp0[i][m])
单个状态时间复杂度O(n),总时间复杂度:O(nMn)
枚举前置状态dp1,类似。

动态规划的填表顺序

i,m,x 从小到大

动态规划的初始值

			const int iNotMay = 1e8;vector<vector<int>> dp0(N + 1, vector<int>(N + 1, iNotMay));vector<vector<int>> dp1(N + 1, vector<int>(N + 1,- iNotMay));dp0[0][min(2,N)] = 0;正负iNotMay 表示	非法。转移时忽略非法数据。

动态规划的返回值

max(dp0.back().back(),preSum.back()-dp1.back().back())

错误原因

{1,2,3} Alice 选择{1} Bob 选择{2} Alice选择 {3}
实际上:Bob 选择 {2,3}

错误代码

class Solution {public:int stoneGameII(vector<int>& piles) {vector<int> preSum(1);for (const auto& n : piles) {preSum.emplace_back(n + preSum.back());}const int N = piles.size();const int iNotMay = 1e8;vector<vector<int>> dp0(N + 1, vector<int>(N + 1, iNotMay));vector<vector<int>> dp1(N + 1, vector<int>(N + 1,- iNotMay));dp0[0][min(2,N)] = 0;for (int i = 0; i <= N; i++) {for (int m = 1; m <= N; m++) {if (dp0[i][m] < iNotMay) {for (int x = 1; (x <= m )&&(i + x <= N); x++) {int m1 = max(m, 2 * x);m1 = min(m1, N);dp1[i + x][m1] = max(dp1[i + x][m1], preSum[i + x] - preSum[i] + dp0[i][m]);}}if (dp1[i][m] >= 0) {for (int x = 1; (x <= m) && (i + x <= N); x++) {int m1 = max(m, 2 * x);m1 = min(m1, N);dp0[i + x][m1] = min(dp0[i + x][m1], dp1[i][m]);}}}}int i1 = *std::max_element(dp1.back().begin(), dp1.back().end());		for (const auto& n : dp0.back()) {if (iNotMay == n) { continue; }i1 = max(i1, n);}return i1;}};

动态规划+前缀和

动态规划的状态表示

dp[i]][m] 当前行动者可以选择[1,m]堆石子,已经选择了i堆,当前行动者从剩余的石子中能获得的最大分数。

动态规划的转移方程

后续状态已知,前置状态未知。
枚举前置状态,枚举需求了x堆石头。 1 <= x <= m 且 i+x <= N
dp[i][m] = max(preSum[i+x]-dp[i+x][m1])

动态规划的填表顺序

i从N-1到0,m从1到N , x从1到m

动态规划的初始值

dp[N].assign(N,0)

动态规划的返回值

dp[0][min(2,N)]

代码

核心代码

class Solution {public:int stoneGameII(vector<int>& piles) {vector<int> preSum(1);for (const auto& n : piles) {preSum.emplace_back(n + preSum.back());}const int N = piles.size();vector<vector<int>> dp(N + 1, vector<int>(N + 1, 0));for (int i = N - 1; i >= 0; i--) {for (int m = 1; m <= N; m++) {int& cur = dp[i][m];for (int x = 1; (x <= m) && (i + x <= N); x++) {int m1 = max(2 * x, m);m1 = min(m1, N);cur = max(cur, preSum.back() - preSum[i] - dp[i + x][m1]);}}}			return dp[0][min(2,N)];}};

单元测试

	vector<int> piles;TEST_METHOD(TestMethod1){piles = { 1};auto res = Solution().stoneGameII(piles);AssertEx(1, res);}TEST_METHOD(TestMethod2){piles = { 1,2 };auto res = Solution().stoneGameII(piles);AssertEx(3, res);}TEST_METHOD(TestMethod3){piles = { 1,2,3 };auto res = Solution().stoneGameII(piles);AssertEx(3, res);}TEST_METHOD(TestMethod4){piles = { 1,2,3,4 };auto res = Solution().stoneGameII(piles);AssertEx(5, res);}TEST_METHOD(TestMethod11){piles = { 2, 7, 9, 4, 4 };auto res = Solution().stoneGameII(piles);AssertEx(10, res);}TEST_METHOD(TestMethod12){piles = { 1,2,3,4,5,100 };auto res = Solution().stoneGameII(piles);AssertEx(104, res);}

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。


http://www.ppmy.cn/ops/118683.html

相关文章

Springboot中基于注解实现公共字段自动填充

1.使用场景 当我们有大量的表需要管理公共字段&#xff0c;并且希望提高开发效率和确保数据一致性时&#xff0c;使用这种自动填充方式是很有必要的。它可以达到一下作用 统一管理数据库表中的公共字段&#xff1a;如创建时间、修改时间、创建人ID、修改人ID等&#xff0c;这些…

docker build 有时候不展示命令的输出情况,怎么办?

来源&#xff1a;https://stackoverflow.com/questions/64804749/why-is-docker-build-not-showing-any-output-from-commands docker build 有时候不展示命令的输出情况&#xff0c;不方便我们 debug&#xff0c;怎么办&#xff1f; 可以加上 docker build --progressplain …

鸿蒙媒体开发系列15——图片解码(PixcelMap)

如果你也对鸿蒙开发感兴趣&#xff0c;加入“Harmony自习室”吧&#xff01;扫描下方名片&#xff0c;关注公众号&#xff0c;公众号更新更快&#xff0c;同时也有更多学习资料和技术讨论群。 1、概述 应用开发中的图片开发是对图片像素数据进行解析、处理、构造的过程&#x…

【Linux】环境变量(初步认识环境变量)

文章目录 1. 环境变量1.1 基本概念 2. 认识常见环境变量2.1 PATH2.2 HOME2.3 SHELL2.4 PWD2.5 USER 3. 理解环境变量 1. 环境变量 在main函数的命令行参数中&#xff0c;有argc、argv、env三个参数。 argc&#xff1a;命令函参数的个数argc&#xff1a;存放每个参数的具体数值…

MySQL 基础语法详解

在信息爆炸的时代&#xff0c;数据成为了宝贵的资源。如何有效地管理和利用数据&#xff0c;成为了每个企业和个人都需要面对的挑战。MySQL作为一款开源的关系型数据库管理系统&#xff0c;凭借其强大的功能和便捷的操作&#xff0c;成为了数据管理领域的主流选择。而掌握MySQL…

手写SpringMVC(简易版)

在上一篇博客中说到这里我们要进行手写SpringMVC&#xff0c;因此最好是将上一篇博客中的SpringMVC源码分析那一块部分搞懂&#xff0c;或者观看动力节点老杜的SpringMVC源码分析再来看这里的书写框架。 首先我们要知道对于一个完整系统的参与者&#xff08;即一个完整的web项…

Spring Boot管理用户数据

目录 学习目标前言Thymeleaf 模板JSON 数据步骤 1: 创建 Spring Boot 项目使用 Spring Initializr 创建项目使用 IDE 创建项目 步骤 2: 添加依赖步骤 3: 创建 Controller步骤 4: 新建index页面步骤 5: 运行应用程序 表单提交步骤 1: 添加 Thymeleaf 依赖在 Maven 中添加依赖 步…

轻量级日志管理系统SpringBoot3+Loki+grafana的使用实例

目录 文章目录 目录1、简介2、SpringBoot3应用发送日志到Loki2.1、基本介绍2.2、添加依赖2.3、配置文件application.yml2.4、创建logback配置2.5、添加日志示例2.6、运行SpringBoot3 3、在grafana中查看日志3.1、登录grafana3.2、查询日志3.3、查询我们的SpringBoot发送过来的日…