博弈论
公平组合游戏ICG
若—个游戏满足:
- 由两名玩家交替行动;
- 在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关;
- 不能行动的玩家判负;
则称该游戏为一个公平组合游戏。
NIM博弈属于公平组合游戏,但城建的棋类游戏,比如围棋,就不是公平组合游戏。因为围棋交战双方分别只能落黑子和白子,胜负判定也比较复杂,不满足条件 和条件 。
可以看出,公平组合游戏不存在平局,而且一定可以结束。
Nim游戏
问题:给定
问如果两人都采用最优策略,先手是否必胜。
结论:如果所有石子数的异或和不等于
正确性证明:
设第
- 如果每堆石子数都为
,那么一定是先手必败,此时异或和为 。 - 如果异或和
不是 。设 在二进制下最高位 为 ,那么一定有一个 的二进制下第 位为 ,那么一定有 ,那么我们可以从第 堆拿走 个石子,那么 就变成了 ,对于整体的异或和,就相当于又异或了一个 , ,所以我们一定可以一步吧当前 变为 ,使得对手每次面对的都是 。
- 如果
是 。我们拿完石子后一定不能让 依旧为 。反证法:设我们拿完石子后可以让 为 ,那么我们从第 堆拿 颗石子,那么新异或和 就等于 ,因为 ,所以 ,所以 ,所以 。而 ,所以假设失败,也就是证明了我们拿完石子后不可能让 依旧为 。
总结一下,主要有三条关系:
- 如果
都等于 ,那么一定先手必败。 - 我们一定可以一步吧当前
变为 。 - 我们拿完石子后不可能让
依旧为 。
也就是说我们一定可以让,对手面对的每次都是 。但他无法改变,到我时 。又因为石子总数是不断减少的,所以他一定会先面临到 都等于 的状态,此时无法移动,也就盘他为失败,对于我就是胜利。说明结论成立。
在这里就可以看出,先手必胜态为
值得注意的是,这个结论表述不是唯一的,但是和别的结论表述是等价的。
例题
#include <iostream> #include <cstring> #include <algorithm> using namespace std; int n, m; int main() { cin >> n; while (n -- ) { int a; scanf("%d", &a); m ^= a; } if (m == 0) cout << "No"; else cout << "Yes"; return 0; }
#include <iostream> #include <algorithm> #include <cstring> using namespace std; int n, m; int main() { cin >> n; int res = 0; for (int i = 1; i <= n; i ++ ) { int a; scanf("%d", &a); if (i & 1) res ^= a; } if (res) puts("Yes"); else puts("No"); return 0; }
P1247 取火柴游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
绿色的水题,看懂上面的 Nim 游戏就能做出来。
#include <iostream> #include <cstring> #include <algorithm> #include <cstdio> using namespace std; const int N = 500010; int n, m; int g[N]; int main() { cin >> n; int res = 0; for (int i = 1; i <= n; i ++ ) { scanf("%d", &g[i]); res ^= g[i]; } if (res) { int k = 0; for (int i = 31; i >= 0; i -- ) { if (res >> i & 1) { k = i; break; } } for (int i = 1; i <= n; i ++ ) { if (g[i] >> k & 1) { cout << g[i] - (g[i] ^ res) << ' ' << i << endl; g[i] = g[i] ^ res; break; } } for (int i = 1; i <= n; i ++ ) cout << g[i] << ' '; } else puts("lose"); return 0; }
P1288 取数游戏 II - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这题就是简单的不知道怎么做,和博弈有关系,但完全没必要。越想多越复杂
反 Nim 游戏
玩法和 Nim 游戏一样,只不过胜利标准变成了,不可移动为胜利。
结论:先手必胜条件为(下面为先手必胜的两种方式):
- 所有
,且 的数量为偶数,则先手必胜。 - 当至少有一个
,且所有 的异或和 为不等于 ,即 。
证明:
对于结论
对于结论
(1)当
(2)当
- 当
时。此时根据 Nim 游戏可知,操作一次后 必定不等于 ,即变成下面的 2. ;或者操作一次后 仅剩一个,即回 ,即给对方为必胜态。 - 当
时,根据 Nim 游戏可知,此时一定可以让 ,且 个数一定大于 (如果只有一个 ,那么就和 中 相悖)。即回到 1.。
可以看出
把
SG函数
基本定义
Mex运算
设
有向图游戏
给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。
SG函数
在有向图游戏中,对于每个节点
特别地,整个有向图游戏 G 的 SG 函数值被定义为有向图游戏起点
使用方法
一般的博弈论问题都可以转化为 SG 函数求解。
以上为基本定义,现在来说说是干什么的。
一般 SG 函数形式为:
首先终止状态的 SG 值为 0,即:
如图可知,每个 SG 值不为
因此对于两个绝顶聪明的人,这类游戏在开始就决定了胜负。
上面是一个图的情况。如果这个图有很多个会怎么样?即一个游戏,既可以在一个图上移动,也可以在其他图上移动。那么一个状态,有很多 SG 值,这时候就把每个图起始的 SG 函数都异或起来,变成一个异或和
注意有的题目需要计算 SG 函数,但是有的题可以不用,如最上面两个 Nim 游戏,直接运用结论就很快,当然也可以算 SG 函数,Nim游戏比较特殊,每堆石子数恰好就是它的 SG 函数值。
例题
#include <iostream> #include <algorithm> #include <cstring> #include <unordered_set> using namespace std; const int N = 10010; int n, m; int g[N], f[N]; int sum; int sg(int x) { if (f[x] != -1) return f[x]; unordered_set<int> s; for (int i = 1; i <= n; i ++ ) if (x - g[i] >= 0) s.insert(sg(x - g[i])); for (int i = 0; ; i ++ ) if (!s.count(i)) return f[x] = i; } int main() { cin >> n; for (int i = 1; i <= n; i ++ ) cin >> g[i]; cin >> m; memset(f, -1, sizeof f); while (m -- ) { int s; cin >> s; sum ^= sg(s); } if (sum) puts("Yes"); else puts("No"); return 0; }
894. 拆分-Nim游戏 - AcWing题库
这个也简单,但是涉及了 SG 函数的本质。
#include <iostream> #include <cstring> #include <algorithm> #include <unordered_set> using namespace std; const int N = 110; int n, m; 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 ++ ) { int a = sg(i); for (int j = 0; j <= i; j ++ ) { int b = sg(j); s.insert(a ^ b); } } for (int i = 0; ; i ++ ) { if (s.count(i) == 0) return f[x] = i; } } int main() { cin >> n; memset(f, -1, sizeof f); int res = 0; for (int i = 1; i <= n; i ++ ) { int a; cin >> a; res ^= sg(a); } if (res) puts("Yes"); else puts("No"); return 0; }
拓展
博弈论的核心:保持自己在可胜态,且让对手无法改变其可败态或者改变我的可胜态。
可胜态,不是必胜态,但只要一直保持自己是可胜态,最后就必胜。同理,可败态不是必败态,但只要能让他一种在可败态,那么就是必败。
阻止对方所有能让我变成非可胜态的举动。(必胜态主导必败态,也就是说除非必胜放水,必败态无法转移到必胜态)。
另一种解释:如果一个状态可以到达一个必败态,那么它就是必胜态;如果一个状态不能到达一个必败态,他就是必败态。
例题
以下的题较难。
1321. 取石子 - AcWing题库
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现