Nim 游戏 、⽯头游戏1、石头游戏2

文章目录

  • Nim 游戏 、⽯头游戏1、石头游戏2
    • **一:Nim 游戏**
    • **二:⽯头游戏**
    • **三、石头游戏2**
      • **方法一:DP 函数**
      • **方法二:DP table**

一:Nim 游戏

你和你的朋友,两个人一起玩 Nim 游戏:桌子上有一堆石头,每次你们轮流拿掉 】1 - 3 块石头。 拿掉最后一块石头的人就是获胜者。你作为先手。你们是聪明人,每一步都是最优解。 编写一个函数,来判断你是否可以在给定石头数量】的情况下赢得游戏。

示例:

输入: 4
输出: false
解释: 如果堆中有 4 块石头,那么你永远不会赢得比赛;因为无论你拿走 1 块、2 块 还是 3 块石头,最后一块石头总是会被你的朋友拿走

解题思路:

我们解决这种问题的思路⼀般都是反着思考:

1 . 如果我能赢, 那么最后轮到我取⽯⼦的时候必须要剩下 1~3 颗⽯⼦, 这样 我才能⼀把拿完。

2.如何营造这样的⼀个局⾯呢? 显然, 如果对⼿拿的时候只剩 4 颗⽯⼦, 那么⽆论他怎么拿, 总会剩下 1~3 颗⽯⼦, 我就能赢。

3.如何逼迫对⼿⾯对 4 颗⽯⼦呢? 要想办法, 让我选择的时候还有 5~7 颗⽯⼦, 这样的话我就有把握让对⽅不得不⾯对 4 颗⽯⼦。

4.如何营造 5 ~ 7 颗⽯⼦的局⾯呢? 让对⼿⾯对 8 颗⽯⼦, ⽆论他怎么拿, 都会给我剩下 5~7 颗, 我就能赢。

5.这样⼀直循环下去, 我们发现只要踩到 4 的倍数, 就落⼊了圈套, 永远逃不出 4 的倍数, ⽽且⼀定会输。 所以这道题的解法⾮常简单:

bool canWinNim(int n) {// 如果上来就踩到 4 的倍数, 那就认输吧
// 否则, 可以把对⽅控制在 4 的倍数, 必胜
return n % 4 != 0;
}

二:⽯头游戏

亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i] 。

游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。

亚历克斯和李轮流进行,亚历克斯先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。

假设亚历克斯和李都发挥出最佳水平,当亚历克斯赢得比赛时返回 true ,当李赢得比赛时返回 false 。

示例:

输入:[5,3,4,5]
输出:true
解释:
亚历克斯先开始,只能拿前 5 颗或后 5 颗石子 。
假设他取了前 5 颗,这一行就变成了 [3,4,5] 。
如果李拿走前 3 颗,那么剩下的是 [4,5],亚历克斯拿走后 5 颗赢得 10 分。
如果李拿走后 5 颗,那么剩下的是 [3,4],亚历克斯拿走后 4 颗赢得 9 分。
这表明,取前 5 颗石子对亚历克斯来说是一个胜利的举动,所以我们返回 true 。

提示:

  1. 2 <= piles.length <= 500
  2. piles.length 是偶数。
  3. 1 <= piles[i] <= 500
  4. sum(piles) 是奇数。

强调双⽅都很聪明的原因, 算法也是求最优决策过程下你是否能赢。这道题⼜涉及到两⼈的博弈, 也可以⽤动态规划算法暴⼒试, ⽐较⿇烦。 但我们只要对规则深⼊思考, 就会⼤惊失⾊: 只要你⾜够聪明, 你是必胜⽆疑的, 因为你是先⼿。


boolean stoneGame(int[] piles) {return true;
}

这是为什么呢, 因为题⽬有两个条件很重要: ⼀是⽯头总共有偶数堆, ⽯头的总数是奇数

这两个看似增加游戏公平性的条件, 反⽽使该游戏成为了⼀个割⾲菜游戏。

我们以 piles=[2, 1, 9, 5] 讲解, 假设这四堆⽯头从左到
右的索引分别是 1, 2, 3, 4。

  • 如果我们把这四堆⽯头按索引的奇偶分为两组, 即第 1、 3 堆和第 2、 4 堆,
  • 那么这两组⽯头的数量⼀定不同, 也就是说⼀堆多⼀堆少。 因为⽯头的总数是奇数, 不能被平分。
  • ⽽作为第⼀个拿⽯头的⼈, 你可以控制⾃⼰拿到所有偶数堆, 或者所有的奇数堆。
  • 你最开始可以选择第 1 堆或第 4 堆。 如果你想要偶数堆, 你就拿第 4 堆, 这样留给对⼿的选择只有第 1、 3 堆, 他不管怎么拿, 第 2 堆⼜会暴露出来,你就可以拿。
  • 同理, 如果你想拿奇数堆, 你就拿第 1 堆, 留给对⼿的只有第2、 4 堆, 他不管怎么拿, 第 3 堆⼜给你暴露出来了。
  • 也就是说, 你可以在第⼀步就观察好, 奇数堆的⽯头总数多, 还是偶数堆的⽯头总数多, 然后步步为营, 就⼀切尽在掌控之中了。

三、石头游戏2

亚历克斯和李继续他们的石子游戏。许多堆石子 排成一行,每堆都有正整数颗石子 piles[i]。游戏以谁手中的石子最多来决出胜负。

亚历克斯和李轮流进行,亚历克斯先开始。最初,M = 1。

在每个玩家的回合中,该玩家可以拿走剩下的 前 X 堆的所有石子,其中 1 <= X <= 2M。然后,令 M = max(M, X)。

游戏一直持续到所有石子都被拿走。

假设亚历克斯和李都发挥出最佳水平,返回亚历克斯可以得到的最大数量的石头。

示例:

输入:piles = [2,7,9,4,4]
输出:10
解释:
如果亚历克斯在开始时拿走一堆石子,李拿走两堆,接着亚历克斯也拿走两堆。在这种情况下,亚历克斯可以拿到 2 + 4 + 4 = 10 颗石子。
如果亚历克斯在开始时拿走两堆石子,那么李就可以拿走剩下全部三堆石子。在这种情况下,亚历克斯可以拿到 2 + 7 = 9 颗石子。
所以我们返回更大的 10。

提示:

  • 1 <= piles.length <= 100
  • 1 <= piles[i] <= 10 ^ 4

解题思路:

定义

  • stones:与题目中 piles 意思保持一致;
  • n:stone个数;
  • score[i, j]:当前玩家从 stones[i] 开始,且 M = j 时,该玩家所能得到的最高分数;
  • sum[i]:从stones[i]开始,剩下石头分数之和(即,stones[i] + … + stones[n - 1]);

考虑

  • 如何使得当前玩家所能获得的分数score[i, j]最高?取1个?取2个?… 取 2 * j 个?
  • 当前玩家拿1个stone时,另一个玩家接下来所能获得的最高分数为score[i + 1, max(j, 1)]
  • 当前玩家拿2个stone时,另一个玩家接下来所能获得的最高分数为score[i + 2, max(j, 2)];
  • 当前玩家拿2*j个stone时,另一个玩家接下来所能获得的最高分数为score[i + 2 * j, max(j, 2 * j)];
  • 因为sum[i]是一定的,只需要使另一个玩家接下来所能获得的最高分数最小,即可保证当前玩家所得到的分数score[i, j]最大

基于上述考虑,得到递归方程如下:


score[i,j]=sum[i] − min(socre[i + 1,max(j,1)],...,score[i + 2 ∗ j,max(j,2 ∗ j)])

进一步考虑,递归终止条件:

因为所有stone的分数都为正数;当 n - i <= 2 * j 时(即,当前玩家可以直接拿下剩下的所有stone),则当前玩家所得最高分 score[i, j] 直接为剩下所有stone分数之和(即,score[i, j] = sum[i]);

完整递归方程如下:

回到题目

  • 一场游戏,Alex所能获得的最高分为score[0,1]
  • 对应的,Lee所获得的分数为 sum[0]−score[0,1]
  • 谁获得的分数高,谁赢

为了可以在常数时间内计算到sum[i],我们需要提前计算好后缀和数组;

当n = 8时,示例如下:

正如前面所说,当 2 * j > n - i 时,当前玩家可以拿走剩下的所有stone,因此这里 j 的上限,我们取 n 的一半向上取整;

为了计算score[0, 1],我们需要知道score[1, 1],score[2, 2],所以计算时我们采用从下到上(从左到右或者从右到左都行)的计算顺序;

方法一:DP 函数

class Solution {public:int n = 0;//表示从i开始取M个获取的最大价值int* vec;//备忘录int mem[105][105];//dp函数int dp(int s, int M) {//如果在备忘录中直接在备忘录中返回,避免重复计算if(mem[s][M] != 0) return mem[s][M];//如果s >= n代表下标已经越界,相当于piles越界if(s >= n) return 0;//如果发现 s + 2 * M >= n 说明玩家可以一次取完,直接返回if(s + 2 * M >= n){return vec[s];}int ans = 0;for(int i = 1; i <= 2 * M; i++) {//当前的最大价值为,当前剩余价值 - 下一个状态的最大价值。ans = max(ans, vec[s] - dp(s + i, max(i, M)));            }//记忆化搜索mem[s][M] = ans;return ans;}int stoneGameII(vector<int>& piles) {n = piles.size();//初始化相关操作memset(mem, 0, sizeof(mem));vec = new int[n];//从 后往前计算每个 i 位置前 的所有石头之和int sum = 0;for(int i = n - 1; i >= 0; i--) {sum += piles[i];vec[i] = sum;}//递归调用dp函数求结果,0:代表玩家从piles的0下标处开始可以获得的最大石头数量 ;1:代表m的初始值为1return dp(0,1);}
};

方法二:DP table

class Solution {public:int stoneGameII(vector<int>& piles) {int len = piles.size();//用来从前往后计算每个 i 位置的所有石头之和int sum = 0; // dp[i][j]表示当前是第i波,m = j,玩家所能获得的最大石头的数量;vector<vector<int>>dp(len + 1, vector<int>(len + 1, 0));//注意要反向遍历for(int i = len - 1; i >= 0; i--){sum += piles[i];// 表示当前所剩下的所有棋子的和for(int M = 1; M <= len; M++){//代表玩家可以一次取完剩下的所有石头,直接放到dp中,继续循环测试if(i + 2 * M >= len){//直接把剩下的所有石头保存在dp数组中dp[i][M] = sum;continue;}//用循环来枚举所有的情况,取其中玩家可以获得最大石头数量的一个结果保存//x代表每次取的堆数//i + x <= len 是为了防止越界//x <= 2 * M 代表每次可以取的石头的堆数范围for(int x = 1; i + x <= len && x <= 2 * M; x++){//每次选取其中结果最大 的来保存dp[i][M] = max(dp[i][M], sum - dp[i + x][max(M, x)]);}}}return dp[0][1];}
};

Nim 游戏 、⽯头游戏1、石头游戏2相关推荐

  1. Java黑皮书课后题第3章:*3.17(游戏:剪刀、石头、布)编写可以玩流行的剪刀-石头-布游戏的程序

    *3.17(游戏:剪刀.石头.布)编写可以玩流行的剪刀-石头-布游戏的程序 题目 题目概述 运行示例 ***特别注意*** 破题 代码 题目 题目概述 *3.17(游戏:剪刀.石头.布)编写可以玩流行 ...

  2. 2022-2028全球虚拟现实游戏头戴设备行业调研及趋势分析报告

    据恒州诚思调研统计,2021年全球虚拟现实游戏头戴设备市场规模约 亿元,2017-2021年年复合增长率CAGR约为%,预计未来将持续保持平稳增长的态势,到2028年市场规模将接近 亿元,未来六年CA ...

  3. c++开发游戏头文件

    文章目录 1.引言 2.头文件 3.判断按键 4.函数 1.引言 为了简化游戏开发,所以写了一个游戏头文件方便使用. 2.头文件 #include <bits/stdc++.h> #inc ...

  4. 游戏开发之捡石头之路--开篇

    为啥要学Unity和C# 学一样东西之前,我得知道为啥吧. 1.当前游戏开发主流引擎 1.Unity; 2.UE4/UE5; 3.Cocos; 4.Laya; 5.其他 主要优劣之分(影响我选择的因素 ...

  5. 手把手教你架构3d游戏引擎pdf_一个在游戏行业摸爬滚打了十几年的人,为何我对这本书情有独钟...

    Big News!<游戏开发:世嘉新人培训教材>今日开始预售啦!经过漫长的等待,这次终于可以买到了.现在下单,你将在图书出印厂的第一时间收到书哦- 这本书由世嘉一线开发者执笔,并被选为世嘉 ...

  6. Love2D游戏引擎制作贪吃蛇游戏

    预览游戏 love2d游戏引擎重要函数 详情: love.load:当游戏开始时被调用且仅调用一次 love.draw:回调函数,每帧更新一次游戏画面 love.update:回调函数,每帧更新一次游 ...

  7. 《MFC游戏开发》笔记七 游戏特效的实现(一):背景滚动

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9344721 作者:七十一雾央 新浪微博:http:// ...

  8. 用java写猜拳游戏,Java写人机猜拳游戏(可扩展其他游戏或其他参与者)

    初学Java,写一个控制台输入输出的小游戏,模拟人机猜拳.为了扩展性稍微强一些,这个小游戏代码有些复杂,但确实可以扩展其他游戏或者其他参与者的. 代码还有一些小问题,后面贴出来... /******* ...

  9. 走进游戏中的美术:游戏美术风格介绍

    本文首发网易游戏学院 如果把游戏比作一场舞蹈演出,游戏的核心玩法是舞蹈本身,那么游戏的美术风格则是舞蹈演员,一位优秀的舞蹈演员能更好的与舞蹈结合,传达舞蹈寓意,赋予舞蹈生命,展现舞蹈的灵魂,美术风格即 ...

最新文章

  1. ScrollView嵌套ListView处理事件冲突
  2. systemd的程序自启动脚本编写
  3. 【数据分析】关于学习SQL的五个常见问题?
  4. linux 手动配置ip地址方法
  5. Queue 队列的用法
  6. linux网络保存退出,linux编辑文件后如何保存退出
  7. 求 1-100 之间不能被 3 整除的数之和
  8. mybatis三种(查询,参数传递)
  9. Ubuntu换源失败:Could not get lock /var/lib/apt/lists/lock - open
  10. 与众不同 windows phone (36) - 8.0 新的瓷贴: FlipTile, CycleTile, IconicTile
  11. ajax 异步插入图片到数据库(多图上传)
  12. 用c++自制词法分析器_编译原理笔记 02 词法分析
  13. windows桌面远程连接ubuntu xrdp成功显示
  14. PVE虚拟服务器配置,我与PVE的交往史 篇一:如何使用虚拟机PVE一步一步打造自己想要的ALL IN ONE 主机...
  15. python写一个笔记软件_科学网—python学习笔记(1)——创建应用 - 高雪峰的博文...
  16. 图的更多相关算法-2(最小生成树)
  17. [Java][Android][Process] Process 创建+控制+分析 经验浅谈
  18. Android项目中嵌入Cocos游戏项目
  19. 算法7:求用小矩形覆盖大矩形有多少种方式
  20. 转:__stack_chk_fail栈检查失败

热门文章

  1. freemodbus源码/获取地址
  2. Vue项目从无到有的部署上Github page
  3. 详解JavaScript之神奇的Object.defineProperty
  4. 编程开发之--Oracle数据库--存储过程在out参数中使用光标(3)
  5. vi常用命令与设置(不断修改中)
  6. CCNA学习指南第二章
  7. No symbols have been loaded for this document
  8. HDU - 3364 Lanterns(高斯消元解方程(取模))
  9. HDU - 5692 Snacks(dfs序+线段树)
  10. python代码规范工具_如何检查python3中的代码规范