基础组合游戏合集
简介
本文介绍了博弈论中的七种基础组合游戏,并给出了证明,如有错误之处欢迎指正
Nim博弈
定义
Nim博弈的定义是:
给定 \(n\) 堆物品,第 \(i\) 堆物品有 \(A_i\) 个,两人轮流取,每次可以任选一堆取走任意多个物品,可以取光但不能不取,最后把物品全部取完者胜利
判断先手是否有必胜策略
结论
\(A_1\oplus A_2\oplus\cdots\oplus A_n\not=0\) 时先手必胜
证明
参考 Nim博弈原理与证明
例题
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int m;
while(cin >> m) {
int x, res = 0;
for(int i = 1; i <= m; i++) {
scanf("%d", &x);
res = res ^ x;
}
printf("%s\n", res ? "Yes" : "No");
}
return 0;
}
Nimk博弈
定义
Nimk博弈是Nim博弈的变形,它的定义是:
给定 \(n\) 堆物品,第 \(i\) 堆物品有 \(A_i\) 个,两人轮流取,每次可以从不超过 \(k\) 堆的物品里取走任意多个物品,可以取光但不能不取,最后把物品全部取完者胜利
判断先手是否有必胜策略
结论
计算所有物品数在某一个二进制位上的 \(1\) 的个数再模 \(k+1\) ,若对任意一个二进制位结果都为 \(0\) ,那么先手必败
证明
参考 Nimk博弈原理与证明
例题
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const double PI = 3.1415926535;
int n, m, l, r, k, maxx;
int s[30 + 5], sg[30 + 5];
int main()
{
while(scanf("%d%d%d%d", &n, &m, &l, &r) != EOF) {
k = l / (2.0 * PI * r);
maxx = 0;
for(int i = 1; i <= n; i++) {
scanf("%d", &s[i]);
s[i] = ceil((double)s[i] / (2.0 * PI * r));
sg[i] = s[i] % (k + 1);
maxx = max(maxx, sg[i]);
}
int len = log2(maxx) + 1;
bool win = false;
for(int i = 1; i <= len; i++) {
int s = 0;
for(int j = 1; j <= n; j++) {
s += sg[j] % 2;
sg[j] /= 2;
}
if(s % (m + 1)) {
win = true;
break;
}
}
printf("%s\n", win ? "Alice" : "Bob");
}
return 0;
}
Anti-Nim博弈
定义
Anti-Nim博弈是Nim博弈的变形,它的定义是:
给定 \(n\) 堆物品,第 \(i\) 堆物品有 \(A_i\) 个,两人轮流取,每次可以任选一堆取走任意多个物品,可以取光但不能不取,最后把物品全部取完者失败
判断先手是否有必胜策略
结论
先手必胜当且仅当:
- 每堆的物品数都为 \(1\) 且Nim和为 \(0\)
- 有些堆的物品数大于 \(1\) 且Nim和不为 \(0\)
证明
例题
#include<bits/stdc++.h>
using namespace std;
int main()
{
int t, n, a, res, maxx;
scanf("%d", &t);
while(t--) {
maxx = res = 0;
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a);
res = res ^ a;
maxx = max(maxx, a);
}
if(maxx == 1)
printf("%s\n", res == 0 ? "John" : "Brother");
else
printf("%s\n", res ? "John" : "Brother");
}
return 0;
}
Staircase-Nim博弈
定义
Staircase-Nim博弈是Nim博弈的变形,它的定义是:
给定 \(n\) 堆物品,第 \(i\) 堆物品有 \(A_i\) 个,两人轮流取,每次可以从第 \(k\) 堆中取任意多个物品放到第 \(k-1\) 堆中,第 \(1\) 堆物品可以放到第 \(0\) 堆中,最后无法操作者失败
判断先手是否有必胜策略
结论
先手必败当且仅当奇数堆的物品数异或和为 \(0\)
证明
例题
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAX_N = 1000 + 5;
int p[MAX_N];
int main()
{
int t, n, res;
scanf("%d", &t);
while(t--) {
res = 0;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &p[i]);
sort(p + 1, p + n + 1);
p[0] = 0;
for(int i = n; i >= 1; i -= 2)
res = res ^ (p[i] - p[i - 1] - 1);
printf("%s will win\n", res ? "Georgia" : "Bob");
}
return 0;
}
Bash博弈
定义
Bash博弈的定义是:
有一堆物品,两人轮流取,每次可以取 \(1\) 到 \(m\) 个物品,最后把物品全部取完者胜利
现在给出初始的物品数 \(n\) 和 \(m\) ,判断先手是否有必胜策略
结论
\(m+1\mid n\) 时先手必败
证明
参考 Bash博弈原理与证明
例题
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main()
{
int t;
scanf("%d", &t);
while(t--) {
ll n, m;
scanf("%lld%lld", &n, &m);
printf("%s\n", n % (m + 1) ? "first" : "second");
}
return 0;
}
Fibonacci博弈
定义
Fibonacci博弈的定义是:
有一堆物品,两人轮流取,先手第一次可以取任意个但不能全部取完。之后每次取的数量必须大于等于 \(1\) 且小于等于上次取的数量的两倍,最后把物品全部取完者胜利
现在给出初始的物品数 \(n\) ,判断先手是否有必胜策略
结论
\(n\) 为斐波那契数数时先手必败
证明
例题
#include<bits/stdc++.h>
using namespace std;
int f[200 + 5];
map<int, bool> mp;
int main()
{
f[1] = f[2] = 1;
for(int i = 3; i <= 50; i++) {
f[i] = f[i - 1] + f[i - 2];
mp[f[i]] = true;
}
int x;
while(scanf("%d", &x) != EOF && x)
printf("%s\n", mp[x] ? "Second win" : "First win");
return 0;
}
Wythoff博弈
定义
Wythoff博弈的定义是:
有两堆若干个物品,两人轮流从某一堆物品中取至少一个或同时从两堆中取相同数量的物品,不能不取,最后把物品全部取完者胜利
现在给出两堆物品的数量 \(n,m\) 判断先手是否有策略必胜
结论
如果 \(n\leq m\) 那么 \(n=\lfloor(m-n)(\frac{\sqrt 5 + 1}{2})\rfloor\) 时先手必败
证明
例题
#include<bits/stdc++.h>
using namespace std;
const double r = (sqrt(5.0) + 1.0) / 2.0;
int main()
{
int a, b;
cin >> a >> b;
if(a < b)
swap(a, b);
cout << !(b == (int)((a - b) * r)) << endl;
return 0;
}