NIM游戏/SG函数

news2024/11/16 6:35:04

NIM游戏

先看一下一维 NIM游戏。

有一堆大小为 n 的石子,甲和乙轮流从石堆里面拿石子,不能一次拿掉所有石子,取走最后一个石子的人获胜,甲先开始,谁是必胜的?

显然,谁先手,谁就获胜。那么推广到二维呢?

 

有两堆大小为 n m 的石子,甲和乙轮流从两个石堆里拿石子,每次从一个石堆里拿石子,不能一次拿掉一堆中所有石子,取走最后一个石子的获胜,甲先开始拿,谁是必胜的?

当 n=m 的时候,先手必输。因为先手从一堆中拿 Y 颗,后手也可以从另外一个堆中拿 Y 颗。循环下去,后手必胜。
而如果 n!=m,先手就可以制造两堆相等的石子,使得后手必败。

引入点新概念。当两堆相等时,先手必败,我们将这种状态叫做必败态。记为 P。
当两堆不相等时,先手必胜,将这种状态叫做必胜态,记为 N。

那么,推广到多维,如何确定谁是必胜的呢?
由前两种NIM游戏可以知道,如果所有石堆大小都相等,那么先手是不能直接取得胜利的。这种状态称为平衡态。反之,可以进行一次操作就取得胜利的状态就是非平衡态。平衡态也就是必败态,非平衡态也就是必胜态。
考虑将所有石堆的大小异或起来,如果结果为 0,那么这就是一个平衡态。如果结果不为零,那就是非平衡态。
我们每拿一次石子,都可以将异或时某一位上的值由这位上的 1 的奇偶性决定。因此我们拿石子时可以控制每一位上 1 的奇偶性,也就因此能控制异或出的总结果了。同样的,对手每操作一次,由于必须拿至少一颗石头,就势必将会影响某一位上 1 的数量,状态必然会改变。这就意味着我们在操作的时候,可以实现平衡态和非平衡态之间的转化

如果一开始我们接手的是一个不平衡态,要取胜,就可以反复给对手构造一个平衡态,这是必胜的。
如果一开始接手的是一个平衡态,只要对手足够聪明,就可以让我们每次都拿到一个平衡态。这就是必败的。

SG函数

由刚才的论述可知,必胜态和必败态之间是可以相互转化的。必败态经过一次操作必然会转化为必胜态,必胜态经过一次操作可能是必胜态,也可能是必败态(想想异或的过程)。当一个状态已经转化为能够分出胜负的必败态时,称这个状态是0级必胜点,记作 SG(x)=0(x 描述了当前的状态)。
如果某个状态最少操作一次就能变为0级必胜点,那么这个状态就是1级必胜点,以此类推,有2级,3级……必胜点。而SG函数就是用来描述每个状态到达终末态时所需要的最少的步骤,即描述每个状态是几级必胜点。定义为:

SG(x)=MEX{SG(y)|x−>y}

SG定理

  • 两个同级必胜点(SG函数值相等)组成的游戏是必败的。因为先手如果降低其中一个必胜点的等级,后手可以降低另一个必胜点的相同数量的等级,使先手一直面对两个同级必胜点,最后面对两个1级必胜点,只能将其中一个必胜点变成必败点,这样先手必败。

  • 两个不同级必胜点(SG函数值不同)组合成的的游戏是必胜的,因为先手可以将等级高的必胜点的等级降低到与另一个必胜点相同,这样后手面对的就是由两个同级必胜点构成局面,先手必胜。

对于一个游戏,可以将组成它的每一个游戏的SG函数值异或起来,为 0,则对于先手来说必败,反之对于先手就是必胜的。这就是 SG定理了!

例题:

Fibonacci again and again

三堆大小分别为 n,m,p 的石子,每堆大小均不超过 1000,两个人拿,令 x 为菲波那契数列中的一项,每个人每次只能从一堆里拿 x 个石子,问谁是必胜的。

板题,主要想说说怎么记忆化搜索求SG函数值。

 

int sg[MAXN + 5],f[MAXN + 5];//f为菲波那契数列 int getsg(int num){ if(num == 0)return sg[num] = 0; if(sg[num] != -1)return sg[num]; bool vis[MAXN + 5];//表示从石子数为num可以转换到哪些状态 for(int i = 1; i <= MAXN: i++){ vis[num - f[i]] = 1; getsg(num - f[i]); } for(int i = 0; ; i++){ if(!vis[i])return sg[num] = i;//找mex,求出是几级必胜点 } return sg[num]; }

求出三个数的SG值后,看 SG(n)SG(m)SG(p) 是否为 0 即可得出答案。

A multiplication game

给定一个 n,令 p=1,甲和乙可以每次将 p 乘上一个属于 [2,9] 的数,谁使 p 大于 n 谁就赢。求谁是必胜的。

 

#include<iostream> #include<cstring> #include<map> #define int long long using namespace std; const int MAXN = 1e5; int n; map<int,int> sg,vis; int getsg(int x){ if(x >= n)return sg[x] = 0; if(vis.find(x) != vis.end())return sg[x]; vis[x] = 1; int s[10];//9的十次方已经大于n的最大值了,故sg函数的值最大为9 memset(s,0,sizeof s); for(int i = 2; i <= 9; i++){ s[getsg(x * i)] = 1; } for(int i = 0; i < 10; i++){ if(!s[i])return sg[x] = i; } return sg[x]; } signed main(){ while(~scanf("%lld",&n)){ sg.clear(); vis.clear(); getsg(1); if(sg[1])cout << "Stan wins.\n"; else cout << "Ollie wins.\n"; } }

Cutting Game

两个人在一张大小为 h∗w 纸上面切,每个人每次可以横着切一刀,也可以竖着切一刀,谁切出了 1∗1 大小的纸,谁就获胜,问谁必胜。

注意到状态是二维的,即当前纸的长与宽。注意下SG函数的记录方法。

 

#include<iostream> #include<cstring> using namespace std; const int MAXN = 1e3; int sg[MAXN + 5][MAXN + 6],n,m; int getsg(int x,int y){ if(x < y)swap(x,y); if(sg[x][y] != -1)return sg[x][y]; if(x == 1 && y == 1)return sg[x][y] = 1; bool vis[4001]; memset(vis,0,sizeof vis); for(int i = 2; i <= x - 2; i++){//横着切 vis[getsg(i,y) ^ getsg(x - i,y)] = 1;//每切一刀,就将当前纸分成了两张,也就是分成了两个子游戏,因此取异或的值 } for(int i = 2; i <= y - 2; i++){//竖着切 vis[getsg(x,i) ^ getsg(x,y - i)] = 1; } for(int i = 0; i <= 4000; i++){ if(!vis[i])return sg[x][y] = i; } return sg[x][y]; } int main(){ memset(sg,-1,sizeof sg); for(int i = 2; i <= MAXN; i++){ sg[1][i] = 0; } while(~scanf("%d%d",&n,&m)){ int ans = getsg(n,m); if(ans)cout << "WIN\n"; else cout << "LOSE\n";; } }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/806228.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯单片机第五届国赛 真题+代码

onewire.c /* # 单总线代码片段说明1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。2. 参赛选手可以自行编写相关代码或以该代码为基础&#xff0c;根据所选单片机类型、运行速度和试题中对单片机时钟频率的要求&#xff0c;进行代码调试和修改。 */// #include …

Opencv的Mat内容学习

来源&#xff1a;Opencv的Mat内容小记 - 知乎 (zhihu.com) 1.Mat是一种图像容器&#xff0c;是二维向量。 灰度图的Mat一般存放<uchar>类型 RGB彩色图像一般存放<Vec3b>类型。 (1)单通道灰度图数据存放样式&#xff1a; (2)RGB三通道彩色图存放形式不同&#x…

微服务性能分析工具 Pyroscope 初体验

Go 自带接口性能分析工具 pprof&#xff0c;较为常用的有以下 4 种分析&#xff1a; CPU Profiling: CPU 分析&#xff0c;按照一定的频率采集所监听的应用程序 CPU&#xff08;含寄存器&#xff09;的使用情况&#xff0c;可确定应用程序在主动消耗 CPU 周期时花费时间的位置…

数值线性代数:奇异值分解SVD

本文记录计算矩阵奇异值分解SVD的原理与流程。 注1&#xff1a;限于研究水平&#xff0c;分析难免不当&#xff0c;欢迎批评指正。 零、预修 0.1 矩阵的奇异值 设列满秩矩阵&#xff0c;若的特征值为&#xff0c;则称为矩阵的奇异值。 0.2 SVD(分解)定理 设&#xff0c;则…

❤ Redirected when going from “/login“ to “/“ via a navigation guard错误

❤ vue路由遇到 Redirected when going from “/login“ to “/“ via a navigation guard错误 路由版本&#xff1a;“vue-router”: “^3.5.2”, 添加了路由守卫&#xff0c;然后开始报这个错误&#xff0c; 原因 就是路由版本导致的 解决办法 // 导航守卫限制路由跳转 …

Jenkins插件管理切换国内源地址

一、替换国内插件下载地址 选择系统管理–>插件管理–> Available Plugins 并等待页面完全加载完成、这样做是为了把jenkins官方的插件列表下载到本地、接着修改地址文件、替换为国内插件地址 进入插件文件目录 cd /var/lib/jenkins/updatesdefault.json 为插件源地址…

比较两字符串数组中对应位置元素的大小char.greater()和char.less()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 比较两字符串数组中 对应位置元素的大小 char.greater()和char.less() [太阳]选择题 下列代码最后输出的结果是&#xff1f; import numpy as np x1 np.array([a, bc, D]) print("【显…

go 查询采购单设备事项V3

一、版本说明 本版本在整合上两次的功能基础上&#xff0c;引进ini配置文件的读取事项&#xff0c;快速读取本地配置文件&#xff0c;完成读取设置 第一版&#xff1a;实现了严格匹配模式的查找 https://blog.csdn.net/wtt234/article/details/131979385 第二版&#xff1a;实…

整数转换-C语言/Java

描述 整数转换。编写一个函数&#xff0c;确定需要改变几个位才能将整数A转成整数B。A&#xff0c;B范围在[-2147483648, 2147483647]之间。 示例1&#xff1a; 输入&#xff1a;A 29 &#xff08;或者0b11101&#xff09;, B 15&#xff08;或者0b01111&#xff09; 输出&…

c++数据锁链

题目描述&#xff1a; 创建一个结构体为Node&#xff0c;具有value , next 两个属性&#xff1b; value为整型&#xff0c;用来储存结构体数值&#xff1b; next为Node类型指针&#xff0c;用来指向下一组数据地址&#xff1b; 第1组数据value 5&#xff1b; 第2组数据value …

1400*C. String Equality(greedy)

Example input 4 3 3 abc bcd 4 2 abba azza 2 1 zz aa 6 2 aaabba ddddcc output No Yes No Yes 题意&#xff1a; 字符串a和b&#xff0c;其字母顺序可以任意交换&#xff0c;k个连续的相同字母&#xff0c;可以全部变为大于这个字母的其他字母&#xff08;bb->cc&…

小程序动态隐藏分享按钮

// 禁用分享 wx.hideShareMenu({menus: [shareAppMessage, shareTimeline] })// 显示分享 wx.showShareMenu({withShareTicket: true,menus: [shareAppMessage, shareTimeline] })//私密消息 wx.updateShareMenu({isPrivateMessage: true, })

Docker容器监控之 CAdvisor+InfluxDB+Granfana

通过docker stats命令可以很方便的看到当前宿主机上所有容器的CPU,内存以及网络流量等数据&#xff0c;一般小公司够用了。但是&#xff0c;docker stats统计结果只能是当前宿主机的全部容器&#xff0c;数据资料是实时的&#xff0c;没有地方存储、没有健康指标过线预警等功能…

Tensorflow预训练模型ckpt与pb两种文件类型的介绍

我们在 Tensorflow无人车使用移动端的SSD(单发多框检测)来识别物体及Graph的认识 熟悉了Graph计算图以及在 Tensorflow2.0中function(是1.0版本的Graph的推荐替代)的相关知识介绍 这个tf.function的用法&#xff0c;了解到控制流与计算图的各自作用&#xff0c;无论使用哪种方…

向量vector与erase()

运行代码&#xff1a; //向量vector与erase() #include"std_lib_facilities.h" //声明Item类 struct Item {string name;int iid;double value;Item():name(" "),iid(0),value(0.0){}Item(string ss,int ii,double vv):name(ss),iid(ii),value(vv){}frien…

数据库原理1——《小猫猫大课堂》数据库原理篇

宝子&#xff0c;你不点个赞吗&#xff1f;不评个论吗&#xff1f;不收个藏吗&#xff1f; 最后的最后&#xff0c;关注我&#xff0c;关注我&#xff0c;关注我&#xff0c;你会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的很重要…

分布式锁中的王者方案 - Redission

文章目录 5.1 分布式锁-redission功能介绍5.2 分布式锁-Redission快速入门5.3 分布式锁-redission可重入锁原理5.4 分布式锁-redission锁重试和WatchDog机制5.5 分布式锁-redission锁的MutiLock原理 5.1 分布式锁-redission功能介绍 基于setnx实现的分布式锁存在下面的问题&am…

【如何训练一个中英翻译模型】LSTM机器翻译模型部署之onnx(python)(四)

系列文章 【如何训练一个中英翻译模型】LSTM机器翻译seq2seq字符编码&#xff08;一&#xff09; 【如何训练一个中英翻译模型】LSTM机器翻译模型训练与保存&#xff08;二&#xff09; 【如何训练一个中英翻译模型】LSTM机器翻译模型部署&#xff08;三&#xff09; 【如何…

极简每周计划应用程序WeekToDo

什么是 WeekToDo ? WeekToDo 是一款免费的极简每周计划应用程序&#xff0c;专注于隐私。使用待办事项列表和日历安排您的任务和项目。适用于 Windows、Mac、Linux 或在线。 WeekToDo 是一个免费且开源的极简每周计划程序。借助 WeekToDo&#xff0c;您可以以简单直观的方式定…