博弈论

定义 必胜状态先手必胜的状态必败状态先手必败的状态

通过推理,我们可以得出下面三条定理:

  • 定理 1:没有后继状态的状态是必败状态。
  • 定理 2:一个状态是必胜状态当且仅当存在至少一个必败状态为它的后继状态。
  • 定理 3:一个状态是必败状态当且仅当它的所有后继状态均为必胜状态。

对于定理 1,如果游戏进行不下去了,那么这个玩家就输掉了游戏。

对于定理 2,如果该状态至少有一个后继状态为必败状态,那么玩家可以通过操作到该必败状态;此时对手的状态为必败状态——对手必定是失败的,而相反地,自己就获得了胜利。

对于定理 3,如果不存在一个后继状态为必败状态,那么无论如何,玩家只能操作到必胜状态;此时对手的状态为必胜状态——对手必定是胜利的,自己就输掉了游戏。

组合游戏:

  1. 两个玩家轮流操作
  2. 游戏状态集有限,保证游戏是在有限步后结束
  3. 不会出现平局的
  4. (1, 2, 3)的后继状态都是先手必胜状态,因此(1, 2, 3)先手必败

规则:

  1. 必败状态 < - > 所有后继都是必胜状态
  2. 必胜状态 < - > 至少一个后继是必败状态
  3. 没有后继的状态是必败状态

Ferguson 游戏

解法时间复杂度非常大,数据范围大了就不行

1.开始有两个盒子,分别有m,n颗糖。

2.每次移动是将一个盒子清空而把另一个盒子的一些糖拿到被清空的盒子中,使得两个盒子至少各有一颗糖。

3.显然唯一的终态为(1,1)。

4.最后移动的游戏者获胜,(m,n)先手是胜还是败?

分析:

1.每一步k=m+n一定减小,k从小到大递推(DP)

2.代码输出所有k <20的必败状态

3.状态(n,m)和(m,n)是等价的,只输出了n <= m的情况

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <set>
#define ll long long
#include <map>
using namespace std;
bool dp[50][50] = {0}; // i, j 代表m n ,值代表输赢 int main() {ios::sync_with_stdio(false);dp[1][1] = false;for(int k = 3; k < 99; k++) {for(int n = 1; n < k; n++) {int m = k - n;bool &w = dp[n][k - n];w = false;for(int i = 1; i < n; i++){ // 清空右边的,并从左边的放i 个过来 if(!dp[i][n - i]) w = true; // 如果下一个阶段为必败点,则当前为必胜点 }for(int i = 1; i < m; i++) {if(!dp[i][m - i]) w = true;}}}/*for(int i = 1; i < 50; i++) {for(int j = 1; j <= i; j++) {cout << j << ' ' << i << ' ' << dp[j][i] << endl;}}*/return 0;
}

Chomp! 游戏

m * n 的棋盘,每次可以取走一个方格并拿走它右边和上面的所有方格,拿到左下角1 * 1的人输

给出m,n 问先生必胜还是必败? 答案:先手必胜

那么现在假设先手取最右上角的方格(m,n) ,接下来后手可以取某块石头(a, b) 使得自己进入必胜的局面。

事实上,先手在第一次取的时候就可以取石头(a, b) ,之后完全模仿后手的必胜步骤,迫使后手失败。

于是产生矛盾。因此不存在后手必胜策略,先手存在必胜策略。

注意:这个证明是非构造性存在性证明,也即只是证明了先手必胜策略的存在性,但没有构造出具体必胜策略。

虽然对于一些特殊的情况,比如棋盘是正方形、棋盘只有两行,可以找到必胜策略;但对于一般情况,还没有人能具体给出Chomp的一般性必胜策略。

约数游戏

1 ~ n 个数字。两个人轮流选择一个数,并把它和它的约数擦去,擦去最后一个数的人获胜,问谁会获胜,试着证明:

假设后手必胜: 那么后手有必胜策略。 假设先手拿1, 后手拿x (必胜点),那为啥不先拿x,直接赢了。。1是任何数的约数

Nim 游戏

有若干堆石子,每堆石子的数量ai都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。

(a, b, c) 为必败状态 <-> a ^ b ^ c = 0,称为Nim和

  1. (13, 12, 8) 的Nim和:为 9, 所以先手胜

如何取胜呢? 给对手一个必败状态呀 13 个 拿走4

适用于任一堆,全部异或

除法游戏

n * m 矩阵,元素为2 - 10000的正整数,每次可以选一行中的一个或多个大于1的整数,把他们中的每个数都变成它的某个因子,比如12可以变成1, 2, 3, 4, 6。不能操作的输(面临所有数都是1的情况)

分析:

  1. 每个数包含素因子个数,让一个数变成其因子,等价于拿掉一个或多个因子
  2. 每行对应一个火柴堆,每个数的素因子都可以看作是一根火柴

SG函数和SG定理

  1. 任意状态x,定义SG(x) = mex(S), S = {SG(y) | y 是x 的后继状态}

  2. mex(S) 表示不在S 内的最小非负整数

    • 若x 有5个后继状态,SG值分别为0, 1, 1, 2, 4,则SG(x) = 3,
  3. 终态的SG值显然为0,而其他值递推得出

  4. SG(x) = 0推出x为必败状态

  5. SG定理:游戏和SG函数等于各子游戏SG函数的Nim和

  6. 可以把各个游戏分而治之,简化问题

    • Bouton 定理可以看作SG定理在Nim 游戏中的直接应用,因为单堆Nim 游戏的SG函数满足SG(x) = x

石子游戏

n堆石子,分别a1, a2…an (a的大小在 long long 内)。两个游戏者轮流操作,每次选一堆,拿走至少一个石子,但不能拿走超过一半的石子。比如若有三堆石子,每堆分别有5, 1, 2个,则在下一轮中,游戏者可以从第一堆拿走1个或两个,第二堆不能拿,第三堆只能拿一个,谁不能拿石子就算输。

分析:

  1. 和Nim游戏不同,但是可以看作n个单堆游戏之和

  2. a 的范围很大,不能按照定义推出所有SG的函数值

  3. 写一个递推程序,看看单堆游戏的SG函数有无规律

    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <vector>
    #include <set>
    #define ll long long
    #include <map>
    using namespace std;
    int main() {ios::sync_with_stdio(false);cin.tie(0);int sg[100], vis[100];sg[1] = 0;cout << sg[1] << ' ';for(int i = 2; i <= 30; i++) {memset(vis, 0, sizeof(vis));for(int j = 1; (j << 1) <= i; j++) {vis[sg[i - j]] = 1;}for(int j = 0; ; j++) {if(!vis[j]) {sg[i] = j;break;}} cout << sg[i] << ' ';}return 0;
    }
    

    打印的结果:0 1 0 2 1 3 0 4 2 5 1 6 3 7 0 8 4 9 2 10 5 11 1 12 6 13 3 14 7 15

  4. n为偶数时,sg(n) = n / 2, 但是为奇数似乎没有什么规律

  5. n为奇数的时候:sg(n) = sg(n / 2)

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <set>
#define ll long long
#include <map>
using namespace std;
ll sg(ll x) {return x % 2 == 0 ? x / 2 : sg(x / 2);
}
int main() {ios::sync_with_stdio(false);cin.tie(0);int t;cin >> t;while(t--) {int n;ll a, v = 0;cin >> n;for(int i = 0; i < n; i++) {cin >> a;v ^= sg(a); // Nim 和}if(v) cout << "YES\n";else cout << "No\n";}return 0;
}

Treblecross 游戏

有n≤200个格子排成一行,其中一些格子里有字符x。两个游戏者轮流操作,每次可以选一个空格,在里面放上字符x。如果此时有3个连续的x出现,则该游戏者赢得比赛。初始情况下不会有3个x连续出现。你的任务是判断先手必胜还是必败,如果必胜,输出所有必胜策略。

分析:

  1. 如果有XX 或者X.X ,一定先手胜

  2. 所以我们不会在一个X的旁边以及旁边的旁边放X

  3. 整个游戏被x 和旁边的“禁区”分成了若干独立片段,每次可选择一个片段进行游戏,谁无法继续游戏就算输,这就是若干游戏的和

    口口OOXOO口口口口口 两边的口为两个子游戏,中间的OX 为禁区

  4. g(x) 为x个格子对应游戏的sg值

  5. g(x) = mex{g(x - 3), g(x - 4), g(x - 5), g(1) ^ g(x - 6), g(2) ^ g(7)…} 边界:g(0) = 0, g(1) = g(2) = g(3) = 1

    1. g(x - 3) 对应把x放在最左边各子,注意到最左边的3个格子都成为了禁区,剩下x - 3个
    2. g(2) ^ g(x - 7) 对应上述中的局面,左边两个各子和右边x - 7 个各子是两个独立的子游戏
  6. 计算初始局面的SG函数。枚举所有策略后继sg值为0的决策就是所求决策

https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=477&page=show_problem&problem=1502

其他情况:每个X旁边都有四个禁区(左右各两个),禁区用i表示,即…iiXii…,显然放到i这个地方是先手必败的

把整张图的禁区全部标记出来,问题变成了往没被标记的区域放X,没放一个X将有iiXii中的i被标记,被标记的格子不能走,问先手必胜/必败

每一段没被标记的区域都是一个子游戏,sg[i]表示长度为i的子游戏sg值

预处理即可

方案只需要枚举第一步走到哪里,重新判断该局面的SG函数是否为0即可

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <set>
#define ll long long
#include <map>
#define maxn 200using namespace std;
int g[maxn + 10];bool winning(char *state) {int n = strlen(state);for(int i = 0;  i < n - 2; i++) {if(state[i] == 'X' && state[i + 1] == 'X' && state[i + 2] == 'X') return false;}int no[maxn + 1]; // no[i] = 1 表示下标为1的格子是"禁区" , 离"X" 距离不超过2memset(no, 0, sizeof(no));no[n] = 1; // 哨兵for(int i = 0; i < n; i++) if(state[i] == 'X') {for(int d = -2; d <= 2; d++) {if(i + d >= 0 && i + d < n) {if(d != 0 && state[i + d] == 'X') return true; // 有两个距离不超过2的'X' 获胜no[i + d] = 1; }}}int sg = 0, start = -1; // 当前子游戏的起点for(int i = 0;  i <= n; i++) {if(start < 0 && !no[i]) start = i;if(no[i] && start >= 0) sg ^= g[i - start];if(no[i]) start = -1;} return sg != 0;
}int mex(vector<int>& s) {if(s.empty()) return 0;sort(s.begin(), s.end());if(s[0] != 0) return 0;for(int i = 1; i < s.size(); i++) {if(s[i] > (s[i - 1] + 1)) return s[i - 1] + 1;}return s[s.size() - 1] + 1;
}
// 预处理g 数组
void init() {g[0] = 0;g[1] = g[2] = g[3] = 1;g[4] = g[5] = 2;for(int i = 4; i <= maxn; i++) {vector<int> s;s.push_back(g[i - 3]); // 放X在最左边 下标为0的格子s.push_back(g[i - 4]); // 放X在下标为1的格子if(i >= 5) s.push_back(g[i - 5]); // 放X在下标为2 的格子for(int j = 3;  j < i - 3; j++) {if(i - j - 3 >= 0) {s.push_back(g[j - 2] ^ g[i - j - 3]); // 子游戏}g[i] = mex(s);}}
}
int main() {ios::sync_with_stdio(false);init();int T;/*for(int i = 0; i < maxn; i++) {cout << g[i] << ' ';}*/cin >> T;while(T--) {char state[maxn + 10];cin >> state;int n = strlen(state);if(!winning(state)) cout << "LOSING\n\n";else {cout << "WINNING\n";vector<int> moves;for(int i = 0; i < n; i++) if(state[i] == '.') {state[i] = 'X';if(!winning(state)) moves.push_back(i + 1);state[i] = '.';}cout << moves[0];for(int i = 1; i < moves.size(); i++) {cout << ' ' << moves[i];}puts("");}}return 0;
}

盒子游戏(BoxGame,UVa12293)

  1. 两个相同的盒子,一个有n(n≤10%)个球,另一个有1个球。Alice,Bob 2人轮流操作。

  2. 每次清空球较少的那个盒子(如果球数相同,任意一个),从另一个里拿些球到这个,两个盒子非空。

  3. 无法操作 <==> (两盒子都只有1球) 者输。判断先手(A)胜还是后手(B)胜。

题解:

  1. 由于每次都是倒掉数量少的那个盒子,再对数量多的盒子进行分割,所以可以把规则简化为:初始时有n个球,每次只能拿走不多于n/2的球,最终状态为1个球,达到这个状态的玩家获胜。

  2. 简化游戏规则之后,可知这是一个典型的SG博弈,但是由于n的范围很大,不能直接求SG值,那就打表找规律,如下:

#include<bits/stdc++.h>
#define ll long long
#define maxn 25
int sg[maxn];
int vis[1000] = {0};
using namespace std;void init() {sg[1] = 0;for(int i = 2; i<=35; i++) {memset(vis, 0, sizeof(vis));for(int j = (i + 1) / 2; j < i; j++) vis[sg[j]] = 1;for(int j = 0; ; j++) if(!vis[j]) {SG[i] = j;break;}}for(int i = 1; i<=32; i++) printf("%-2d ",i);putchar('\n');for(int i = 1; i<=32; i++) printf("%-2d ",sg[i]);putchar('\n');
}
int main() {init();return 0;
}

可知,当n为 2^i - 1时,先手输;否则先手赢

类Nim游戏

有N(N≤20000)堆石子,第i堆有a个(1≤a,$10%)。两人轮流取,每次可以选择一堆,取一或多个(可以取完),但不能同时在两堆或更多堆中取。第一个人可以任选一堆取,但后面每次取时须遵守以下规则:

1.如果对手刚才没有把一堆石子全部取走,则他只能继续在这堆石子里取;

2.只有当对手把一堆石子全部取走时,他才能换一堆石子取。谁取到最后一个石子就赢。假定游戏双方都绝顶聪明,谁会赢?

分析:

  1. 假设m个1, k个其他数组,分情况讨论:

  2. 情况1:k = 0, m若为奇数则先手胜

  3. 情况2:k > 0 :

    1. m为奇数:先手挨个把非1的堆取成1,之后就先手胜
    2. m为偶数:先手把k - 1个非1的堆取成1, 再把最后一个堆取完,给对手一个必败的局面,先手胜
  4. 每个数字为1, 且总数为偶数,先手败

博弈论与 sg 函数相关推荐

  1. 博弈论与SG函数(Nim游戏)

    博弈论与SG函数(Nim游戏) 目录 博弈论与SG函数(Nim游戏) 游戏状态 状态图(SG图) Nim 游戏 Nim 和 SG函数 Grundy数字 组合博弈游戏 Grundy 游戏 例题 在本篇, ...

  2. 算法竞赛进阶指南0x3A 博弈论之SG函数

    算法竞赛进阶指南0x3A 博弈论之SG函数

  3. 《算法竞赛进阶指南》数论篇(3)-组合计数,Lucas定理,Catalan数列,容斥原理,莫比乌斯反演,概率与数学期望,博弈论之SG函数

    文章目录 组合计数 例题:Counting swaps Lucas定理 Cnm≡Cnmodpmmodp∗Cn/pm/p(modp)C_n^m\equiv C_{n\ mod\ p}^{m\ mod\ ...

  4. Cow Digit Game(博弈论:sg函数)

    链接:https://ac.nowcoder.com/acm/contest/1071/G 来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32768K,其他语言6553 ...

  5. 博弈论与sg函数入门

    记录一点结论性的东西,推导见百度吧. 首先博弈的前提是双方"绝对理智". 一般的胜负博弈游戏来说,有以下几点:(注意必胜必败是针对这回合操作的人) 所有终结状态为必败点(比如五子棋 ...

  6. 博弈论之SG函数(NIM博弈、反NIM博弈证明+例题)--POJ2311

    目录 NIM博弈: 题目: 代码: 反NIM博弈: 题目: 代码: 公平组合游戏ICG: 有向图游戏: Mex运算: SG函数: 有向图游戏的和: 定理: 题目: 代码: 参考材料: NIM博弈: 内 ...

  7. hdu1847(博弈论:sg函数)

    Good Luck in CET-4 Everybody! Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Ja ...

  8. 牛客小白赛7 B自杀游戏 (博弈论,SG函数)

    链接:https://www.nowcoder.com/acm/contest/190/B 来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32768K,其他语言6553 ...

  9. 博弈论——关于Nim游戏和SG函数的几个链接

    今天做了一道博弈论的题目,正好去找找相关资料再复习一下. 网上能找到的都是同样的文章,不过我觉得足够了,Nim游戏百度百科里说的很详细,包括公式.证明之类的 http://baike.baidu.co ...

最新文章

  1. 每日一皮:死循环的深刻理解...
  2. 带来高收入的三大竞争力技巧,可以涨到40W了!
  3. VTK:结构化网格之BlankPoint
  4. Linux 的进程状态
  5. 2台服务器负载均衡后synchronized_一篇有趣的负载均衡算法实现
  6. 把握不好数组边界的危害(记洛谷P1789题RE+WA的经历,Java语言描述)
  7. 缩点【洛谷P1262】 间谍网络
  8. OpenCV二值化cvThreshold和自适应二值化cvAdaptiveThreshold及Otsu
  9. JS学习笔记——JavaScript继承的6种方法(原型链、借用构造函数、组合、原型式、寄生式、寄生组合式)...
  10. ITIL 4 Foundation题目-4
  11. 雷电模拟器Android obb,exagear模拟器数据obb包
  12. 计算机文化基础——计算机基础知识
  13. [ 2204听力 ] 一
  14. JS输入语句与输出语句
  15. 360 浏览器页面兼容 IE7
  16. 怎么把备忘录中的视频导到手机相册里
  17. OPPO R2017线刷刷机包 可解账户锁 刷机教程
  18. 第7章第30节:四图排版:四张图片交错对齐排列 [PowerPoint精美幻灯片实战教程]
  19. 创新TX230音箱线控电位器维修记
  20. 项目管理的49个过程

热门文章

  1. POJ 1589 Unix ls
  2. 太原铁警严厉打击倒卖车票 查获假票60张
  3. 调用集成快递公司接口实现快递实时查询的方法
  4. jQuery-图片加载失败时,显示默认图片
  5. CentOS 如何在线乔迁 AlmaLinux 或 Rocky Linux ,且避免数据中心停机
  6. 北大青鸟广州天河中心学员作品
  7. bitnami redmine mysql 密码_Bitnami redmine 安装后查看数据库以及更改url
  8. HTML Emmet语法
  9. docker保存和载入镜像
  10. 语义分割 结果叠加原图像方法