[博弈论] Nim游戏及SG函数(经典+台阶+集合+拆分)
文章目录
- 0. 前言
- 1. Nim 游戏+模板题
- 2. 台阶 - Nim 游戏+变种题
- 3. Mex运算与SG函数
- 4. 集合 - Nim 游戏+变种题
- 5. 拆分 - Nim 游戏+变种题
0. 前言
一堆关于基础博弈论算法题的相关定义:
NIM游戏:
给定N堆物品,第i堆物品有Ai个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但不能不取。取走最后一件物品者获胜。两人都采取最优策略,问先手是否必胜。
我们把这种游戏称为NIM博弈。把游戏过程中面临的状态称为局面。整局游戏第一个行动的称为先手,第二个行动的称为后手。若在某一局面下无论采取何种行动,都会输掉游戏,则称该局面必败。
所谓采取最优策略是指,若在某一局面下存在某种行动,使得行动后对面面临必败局面,则优先采取该行动。同时,这样的局面被称为必胜。我们讨论的博弈问题一般都只考虑理想情况,即两人均无失误,都采取最优策略行动时游戏的结果。
NIM博弈不存在平局,只有先手必胜和先手必败两种情况。
定理: NIM博弈先手必胜,当且仅当 A1 ^ A2 ^ … ^ An != 0
公平组合游戏ICG:
若一个游戏满足:
- 由两名玩家交替行动;
- 在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关;
- 不能行动的玩家判负;
则称该游戏为一个公平组合游戏。
NIM博弈属于公平组合游戏,但城建的棋类游戏,比如围棋,就不是公平组合游戏。因为围棋交战双方分别只能落黑子和白子,胜负判定也比较复杂,不满足条件2和条件3。
有向图游戏:
给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。
任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。
Mex运算:
设S表示一个非负整数集合。定义mex(S)为求出不属于集合S的最小非负整数的运算,即: mex(S) = min{x}, x属于自然数,且x不属于S
SG函数:
在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1, y2, …, yk,定义SG(x)为x的后继节点y1, y2, …, yk 的SG函数值构成的集合再执行mex(S)运算的结果,即: SG(x) = mex({SG(y1), SG(y2), …, SG(yk)}) 特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)。
有向图游戏的和:
设G1, G2, …, Gm 是m个有向图游戏。定义有向图游戏G,它的行动规则是任选某个有向图游戏Gi,并在Gi上行动一步。G被称为有向图游戏G1, G2, …,Gm的和。 有向图游戏的和的SG函数值等于它包含的各个子游戏SG函数值的异或和,即: SG(G) = SG(G1) ^ SG(G2) ^ …^ SG(Gm)
定理:
有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0。
有向图游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0。
1. Nim 游戏+模板题
891. Nim游戏
重点: 式子推导
公式梳理的还是很清楚的,就是字难看…
总结:初始所有石子异或值为 0,则先手必败,异或值不为 0,则先手必胜。
代码:
#include <iostream>using namespace std;int main() {int n, res = 0;cin >> n;while (n --) {int a;cin >> a;res ^= a;}if (res) puts("Yes");else puts("No");return 0;
}
2. 台阶 - Nim 游戏+变种题
892. 台阶-Nim游戏
重点: 式子推导
某些式子需要用到 Nim
游戏的些许证明,翻上去看就行了。字迹潦草…
总结:初始所有奇数阶石子异或值为 0,则先手必败,异或值不为 0,则先手必胜。
思考:为什么不判断偶数级台阶的异或呢?
- 理解方式一:必败态是所有奇数阶台阶的异或和为 0,其余的为必胜态。因为每一个这种定义的必败态不论怎么操作,对手都可以再把你转移回必败态;反之任何一个必胜态,一定存在一种转移到必败态的方式。
- 理解方式二:假如对手处于偶数台阶的异或为 0 的局面,他可以通过把第一个台阶的所有石子全部放到地面上,而轮到我们的时候就只能破坏这个异或为 0 的局面(因为不能移动第 0 个台阶也就是地面,只能移动其他台阶的石子),把必胜的局面(异或为非 0)给了对手。但是作为先手方就是要避免这种情况出现,采取最优策略是不会让对手偶数阶台阶异或为 0,且一号台阶有石子的。那么,当先手方遇到初始偶数阶台阶异或为 0 情况时,拿光 1 号台阶的石子到地面保持自己的必败态?这是不现实的,这就不叫做最优策略,而是最蠢策略了。所以通过判断偶数阶台阶异或为 0 的操作,必胜态与必败态之间的转化是困难的。中间绕的弯很多,暂时没找到很好的策略来解释这个问题。
代码:
#include <iostream>using namespace std;int main() {int n;cin >> n;int res = 0, cnt = 0;while (n --) {int a;cin >> a;++ cnt;if (cnt % 2) res ^= a;}puts(res ? "Yes": "No");return 0;
}
3. Mex运算与SG函数
Mex运算:
设S表示一个非负整数集合。定义mex(S)为求出不属于集合S的最小非负整数的运算,即: mex(S) = min{x}, x属于自然数,且x不属于S
SG函数:
在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1, y2, …, yk,定义SG(x)为x的后继节点y1,y2, …, yk 的SG函数值构成的集合再执行mex(S)运算的结果,即: SG(x) = mex({SG(y1), SG(y2), …,SG(yk)}) 特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)。
- SG(k)有k个后继节点,且分别为 0~k-1
- 非 0 可以走向 0
- 0 只能走向非 0
- 多个独立局面的SG值等于这些局面的SG值的异或值
例:若 S=[2,5]S=[2,5]S=[2,5] 表示每次只能取 2 个或 5 个石子,h=10h=10h=10 表示公有 10 个石子,则有展开形式为:
定理: 对于一个图 G
,如果 SG(G) != 0
,则先手必胜,反之必败
证明:
- 如果
SG(G) = !0
,则根据性质 2,先手必可以走向 0,所以后手遇见的是 0 的局面,然而 0 局面仅对应终点局面或者只能走向非 0 局面。故先手必胜。
定理: 对于 n
个图,如果 SG(G1) ^ SG(G2) ^ ... ^ SG(Gn) ^ != 0
,则先手必胜,反之必败。
证明:
- 类比
Nim
游戏 - 当
SG(Gi) = 0
时,xor = 0
,显然先手必败,简单理解为所有图中均无石子可取。终止状态必为目前这个局面,但是当前局面并不一定为终止状态 - 当
xor = x = !0
,因为肯定存在一个SG(Gi) ^ x < SG(xi)
,而根据SG()
性质 1,SG(k)
可以走到0~k-1
的任何一个状态。因此,必定可以从SG(xi)
走到SG(xi)^x
状态,那么使得xor = 0
- 当
xor = 0
,类比Nim
游戏,每移动一个节点,对应的SG
值必然减小,且xor != 0
。即若xor = 0
,则说明移动的那个节点的值并没有变化,即从SG(k)
变成了k
,但是这与SG
函数性质 1 矛盾。这个证明完全与Nim
游戏证明方式相同。
4. 集合 - Nim 游戏+变种题
893. 集合-Nim游戏
重点: SG 函数,dfs 求解 SG 值
采用记忆化搜索来求解 SG
值,在此还需理解石子相同那么 SG
值也相同的原因
- 因为取法方式唯一,那么所能造成的局面是固定的
- 不论是它作为起点,后序的分支局面都已经确定了
- 还是它作为某个分支中的一个局面,那么它后序的局面也已经确定了
- 所以,相同石子的 SG 值相等。所以可以采用记忆化搜索来加速搜索效率
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>using namespace std;const int N = 105, M = 1e4+5;int n, k;
int s[N], f[M]; // s存取石子的方法,f存取所有取法对应的sg值// dfs求取sg结果
int sg(int x) {if (f[x] != -1) return f[x];unordered_set<int> S; // 哈希表存所有可以到的局面for (int i = 0; i < k; ++i) if (x >= s[i]) S.insert(sg(x - s[i])); for (int i = 0 ; ; ++i) if (!S.count(i))return f[x] = i;
}int main() {cin >> k; // s个固定取法memset(f, -1, sizeof f); // 记忆化搜索数组初始化for (int i = 0; i < k; ++i) cin >> s[i]; cin >> n; // 每堆石子的数量int res = 0; for (int i = 0; i < n; ++i) {int x;cin >> x;res ^= sg(x);}puts(res ? "Yes" : "No");return 0;
}
5. 拆分 - Nim 游戏+变种题
894. 拆分-Nim游戏
重点: SG 函数,dfs 求解 SG 值
思路:
- 这个相较于上题来讲,本题每一堆可以变成不大于原来那堆的任意大小的两堆。这样保证了最大石子可以一直变小,则一定会结束
a
石子分解成b
石子和c
石子,肯定需要保证b
和c
均小于a
。且为了保证不进行重复计算,枚举的时候b
大于等于c
注意下就行了- 则此时局面
a
,拆分成了两个独立局面b
和c
,由于SG
函数性质,多个独立局面的SG
值等于这些局面SG
值的异或 - 所以,哈希表中存的就是
sg(b) ^ sg(c)
这是本题与上题的唯一区别
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>using namespace std;const int N = 105;int n;
int f[N];int sg(int x) {if (f[x] != -1) return f[x];unordered_set<int> S;for (int i = 0; i < x; ++i) for (int j = 0; j <= i; ++j) S.insert(sg(i) ^ sg(j));for (int i = 0; ; ++i)if (!S.count(i))return f[x] = i;
}int main() {cin >> n;memset(f, -1, sizeof f);int res = 0; for (int i = 0; i < n; ++i) {int x;cin >> x;res ^= sg(x);}puts(res ? "Yes" : "No");return 0;}
[博弈论] Nim游戏及SG函数(经典+台阶+集合+拆分)相关推荐
- 博弈论 Nim游戏与SG函数
普通Nim游戏: 有若干堆石子,两人轮流从中取石子,取走最后一个石子的人为胜利者 我们判断先手必胜还是先手必败就要判断先手面对的局面是必胜态还是必败态 并且普通Nim游戏满足以下性质: 1.无法移动的 ...
- 【acm 博弈论 】 之 Nim游戏与sg函数
文章目录 前言 巴什博弈 威佐夫博弈 Nim游戏 Nim游戏与sg函数 题目 题意 样例 思路 代码 前言 从今天开始复习和整理下acm的部分模块,从博弈论开始. 著名的"取石子" ...
- Nim游戏和SG函数
个人感觉这篇文章对Nim游戏和SG函数讲的非常清楚:点击打开链接 (放原文链接应该没关系吧QAQ)
- Nim 游戏和 SG 函数
Nim游戏 Nim游戏定义 Nim游戏是组合游戏(Combinatorial Games)的一种,准确来说,属于"Impartial Combinatorial Games"(以下 ...
- 【学习笔记】NIM游戏与SG函数初探
公平组合游戏ICG 游戏条件介绍: 由两名玩家交替行动 游戏进程的任意时刻,可以执行的合法行动与轮到的玩家无关 若当前玩家无法行动,则判负 则称该组合游戏为公平组合游戏.如NIM游戏等即是经典的公平组 ...
- 博弈论——关于Nim游戏和SG函数的几个链接
今天做了一道博弈论的题目,正好去找找相关资料再复习一下. 网上能找到的都是同样的文章,不过我觉得足够了,Nim游戏百度百科里说的很详细,包括公式.证明之类的 http://baike.baidu.co ...
- Nim游戏入门+SG函数
对于经典的Nim游戏,只需要把每一堆初始状态都异或起来,最后得到的结果非0的为必胜状态,结果为0的为必败状态. 原理:异或的结果非0的状态总能通过一次取物品操作,将此状态转化为结果为0的状态:而异或结 ...
- AcWing 893. 集合-Nim游戏(SG函数)
题目链接 https://www.acwing.com/problem/content/895/ 思路 因为博弈的过程是一个状态交替的过程,而SG函数就是记录每一个状态交替的过程,从终点的必败态往前推 ...
- 组合游戏(Nim游戏)——SG函数
SG为0的点(必败点)能到达的肯定都是SG不为0的点(必胜点): SG不为0的点(必胜点)一定能通过某种方式到达SG为0的点(必败点). 组合游戏典型例题: 给定n堆石子以及一个由k个不同正整数构成的 ...
最新文章
- iptables实现字符串匹配
- matlab算法时间复杂度,关于降低算法时间复杂度问题的探讨和总结
- java与c内存管理_Java基础--Java内存管理与垃圾回收
- boost::fusion::single_view用法的测试程序
- SAP Spartacus storefront 模块的实现位置
- 锯木棍(51Nod-2143)
- SQL DATEADD (Transact-SQL)根据需要返回时间,
- 在eclipse中使用第三方库总结
- .NET单元测试(五):多线程
- 《数学建模算法与应用》第2版 司守奎 孙兆亮及其习题解答两本书的配套程序及数据
- windows10录制视频 电脑屏幕录像
- linux好用的下载工具,四款linux下的好工具
- 优控触摸屏使用手册_中达优控plc触摸屏一体机说明书资料
- python爬虫 | 鸿鹄论坛评论爬取
- 计算机网络介绍,TCP协议,Socket网络编程
- 烂泥:关于安装LiveZilla,MySql数据库的问题
- 看一点逻辑学,试图理解一点真相
- java服务端监控平台设计
- YUI中一些方法总结
- JavaWeb学习-AJAX-3-练习:验证用户名是否存在