Nim 游戏 、⽯头游戏1、石头游戏2
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 。
提示:
- 2 <= piles.length <= 500
- piles.length 是偶数。
- 1 <= piles[i] <= 500
- 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相关推荐
- Java黑皮书课后题第3章:*3.17(游戏:剪刀、石头、布)编写可以玩流行的剪刀-石头-布游戏的程序
*3.17(游戏:剪刀.石头.布)编写可以玩流行的剪刀-石头-布游戏的程序 题目 题目概述 运行示例 ***特别注意*** 破题 代码 题目 题目概述 *3.17(游戏:剪刀.石头.布)编写可以玩流行 ...
- 2022-2028全球虚拟现实游戏头戴设备行业调研及趋势分析报告
据恒州诚思调研统计,2021年全球虚拟现实游戏头戴设备市场规模约 亿元,2017-2021年年复合增长率CAGR约为%,预计未来将持续保持平稳增长的态势,到2028年市场规模将接近 亿元,未来六年CA ...
- c++开发游戏头文件
文章目录 1.引言 2.头文件 3.判断按键 4.函数 1.引言 为了简化游戏开发,所以写了一个游戏头文件方便使用. 2.头文件 #include <bits/stdc++.h> #inc ...
- 游戏开发之捡石头之路--开篇
为啥要学Unity和C# 学一样东西之前,我得知道为啥吧. 1.当前游戏开发主流引擎 1.Unity; 2.UE4/UE5; 3.Cocos; 4.Laya; 5.其他 主要优劣之分(影响我选择的因素 ...
- 手把手教你架构3d游戏引擎pdf_一个在游戏行业摸爬滚打了十几年的人,为何我对这本书情有独钟...
Big News!<游戏开发:世嘉新人培训教材>今日开始预售啦!经过漫长的等待,这次终于可以买到了.现在下单,你将在图书出印厂的第一时间收到书哦- 这本书由世嘉一线开发者执笔,并被选为世嘉 ...
- Love2D游戏引擎制作贪吃蛇游戏
预览游戏 love2d游戏引擎重要函数 详情: love.load:当游戏开始时被调用且仅调用一次 love.draw:回调函数,每帧更新一次游戏画面 love.update:回调函数,每帧更新一次游 ...
- 《MFC游戏开发》笔记七 游戏特效的实现(一):背景滚动
本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9344721 作者:七十一雾央 新浪微博:http:// ...
- 用java写猜拳游戏,Java写人机猜拳游戏(可扩展其他游戏或其他参与者)
初学Java,写一个控制台输入输出的小游戏,模拟人机猜拳.为了扩展性稍微强一些,这个小游戏代码有些复杂,但确实可以扩展其他游戏或者其他参与者的. 代码还有一些小问题,后面贴出来... /******* ...
- 走进游戏中的美术:游戏美术风格介绍
本文首发网易游戏学院 如果把游戏比作一场舞蹈演出,游戏的核心玩法是舞蹈本身,那么游戏的美术风格则是舞蹈演员,一位优秀的舞蹈演员能更好的与舞蹈结合,传达舞蹈寓意,赋予舞蹈生命,展现舞蹈的灵魂,美术风格即 ...
最新文章
- ScrollView嵌套ListView处理事件冲突
- systemd的程序自启动脚本编写
- 【数据分析】关于学习SQL的五个常见问题?
- linux 手动配置ip地址方法
- Queue 队列的用法
- linux网络保存退出,linux编辑文件后如何保存退出
- 求 1-100 之间不能被 3 整除的数之和
- mybatis三种(查询,参数传递)
- Ubuntu换源失败:Could not get lock /var/lib/apt/lists/lock - open
- 与众不同 windows phone (36) - 8.0 新的瓷贴: FlipTile, CycleTile, IconicTile
- ajax 异步插入图片到数据库(多图上传)
- 用c++自制词法分析器_编译原理笔记 02 词法分析
- windows桌面远程连接ubuntu xrdp成功显示
- PVE虚拟服务器配置,我与PVE的交往史 篇一:如何使用虚拟机PVE一步一步打造自己想要的ALL IN ONE 主机...
- python写一个笔记软件_科学网—python学习笔记(1)——创建应用 - 高雪峰的博文...
- 图的更多相关算法-2(最小生成树)
- [Java][Android][Process] Process 创建+控制+分析 经验浅谈
- Android项目中嵌入Cocos游戏项目
- 算法7:求用小矩形覆盖大矩形有多少种方式
- 转:__stack_chk_fail栈检查失败
热门文章
- freemodbus源码/获取地址
- Vue项目从无到有的部署上Github page
- 详解JavaScript之神奇的Object.defineProperty
- 编程开发之--Oracle数据库--存储过程在out参数中使用光标(3)
- vi常用命令与设置(不断修改中)
- CCNA学习指南第二章
- No symbols have been loaded for this document
- HDU - 3364 Lanterns(高斯消元解方程(取模))
- HDU - 5692 Snacks(dfs序+线段树)
- python代码规范工具_如何检查python3中的代码规范