博弈总结
这里是看了学长的例会以及挑战上关于博弈的知识进行一下知识总结
1、PN法
定义:
必败点P:位于此位置的人,在双方操作均优的情况下,必败,记作“0”
必胜点N:位于此位置的人,在双方操作均优的情况下,必胜,记作“1”
性质:
游戏结束的点(通常为0点)均为必败点
必胜点等价于必有一种方式可以到达必败点
必败点等价于所能到的点全为必胜点
2、巴什博弈
\(n\)个石子,A B两个人取,操作者必须取\(1\sim m\)个石子,先取完获胜
结论:必败点 \(n\%\left(m+1\right)=0\) 必胜点\(n\%\left(m+1\right)\ne0\)
证明:(假设\(m=2\))
| n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| PN | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 |
3、SG定理
- 局面的异或:对于平行关系下(也就是一个局面下)的\(sg\)值是所有局面下\(sg\)值的异或
即\(sg\left(n\right)= sg(s_1) \oplus sg(s_2) \oplus ...\oplus sg(s_n)\) - \(mex\)运算:\(mex(A)\)代表最小的不属于A集合的自然数
即\(mex(A)=\{x|x\in\N \wedge\neg \left(\exist y\right)\left( y\in A \wedge y<x\right)\}\) - 当\(sg(n)=0\)时,必败;\(sg(n)\ne0时\),必胜
- 证明方法运用了类比(必胜点的转移和数字的异或是否为0具有相似性)
一个其实用处不大的模板
博主实测,\(mex\)使用vis数组速度远快过set
int sg[maxn],f[maxn];
bool vis[maxn];
void get_sg()
{
memset(sg, 0, sizeof(sg));
for (int i = 1; i < maxn; ++i)
{
memset(vis,0,sizeof(vis));
for (int j = 0; j < N && f[j] <= i; ++j)
vis[sg[i - f[j]]] = 1;
for(int j=0;;++j)
if (!vis[j])
{
sg[i] = j;
break;
}
}
}
4、SG定理的应用
洛谷-P2197 Nim博弈(常规)
\(n\)堆石子,每堆有\(a[i]\)个,A B两人轮流取,每次可以在一堆石子中取任意个(可一次性取完)最后率先取完者获胜
结论:直接套用\(sg\)定理,可以发现每堆石子的\(sg\)值恰好为拥有石子的个数,再求异或和即可
HDU-2873 Bomb Game(搜索求SG值)
结论:利用记忆化搜索求得SG值
#include<bits/stdc++.h>
using namespace std;
int sg[55][55];
int get_sg(int x, int y)
{
if (~sg[x][y])
return sg[x][y];
bool vis[3000] = {0};
if (x > 1 && y > 1)
{
for (int i = 1; i < x; ++i)
for (int j = 1; j < y; ++j)
vis[get_sg(i, y)^get_sg(x, j)]=true;
}
else if (y == 1 && x > 1)
for (int i = 1; i < x; ++i)
vis[get_sg(i, 1)]=true;
else if (x == 1 && y > 1)
for (int i = 1; i < y; ++i)
vis[get_sg(1, i)]=true;
for (int i = 0;; ++i)
if (vis[i] == false)
return sg[x][y] = i;
}
char s[55];
int main()
{
int n, m;
memset(sg, -1, sizeof(sg));
sg[1][1] = 0;
while (~scanf("%d%d", &n, &m) && n + m)
{
int ans = 0;
for (int i = 1; i <= n; ++i)
{
scanf("%s", s + 1);
for (int j = 1; j <= m; ++j)
if (s[j] == '#')
ans ^= get_sg(i, j);
}
printf("%s\n", ans ? "John" : "Jack");
}
}
Gym-101908B Marbles(SG必败点变形)
结论:根据题意可知,尽管\((0,0)\)为初始必败点,但是只需要移动一个弹珠到终点便可胜利,于是可知只有当所有弹珠移动到下一组初始必败点\((1,2)\)和\((2,1)\)时才能决定双方的胜负关系
注意: 这里通过将 “存在一个即获得胜利”\(\rightarrow\)"所有都到达某条件才获得胜利"的思想,使得可以通过\(sg\)定理求解
#include<bits/stdc++.h>
using namespace std;
int sg[105][105];
bool vis[10005];
void get_sg()
{
for (int i = 1; i <= 100; ++i)
{
for (int j = 1; j <= 100; ++j)
{
if (i == j) continue;
memset(vis, 0, sizeof(vis));
for (int k = 1; k <= max(i, j); ++k)
{
if (i - k >= 1 && i - k != j) vis[sg[i - k][j]] = true;
if (j - k >= 1 && j - k != i) vis[sg[i][j - k]] = true;
if (i - k >= 1 && j - k >= 1) vis[sg[i - k][j - k]] = true;
}
for (int k = 0;; ++k)
if (!vis[k])
{
sg[i][j] = k;
break;
}
}
}
}
int main()
{
int N;
int a, b;
bool ok = 0;
int ans = 0;
get_sg();
scanf("%d", &N);
while (N--)
{
scanf("%d%d", &a, &b);
ans ^= sg[a][b];
ok |= a == b;
}
printf("%c", ans || ok ? 'Y' : 'N');
}
HDU-1907 John(anti-SG)
结论:将\(Nim\)博弈变形为谁最先取光谁输,那么如果至少有一个子游戏的\(sg\)值大于1,则就当作普通的\(Nim\)进行处理,否则反过来,\(sg\)值为0时为必胜
#include<bits/stdc++.h>
using namespace std;
int main()
{
int T, n, t;
int ans;
bool ok;
scanf("%d", &T);
while (T--)
{
ans = ok = 0;
scanf("%d", &n);
while (n--)
{
scanf("%d", &t);
ans ^= t;
if (t > 1)
ok = true;
}
printf("%s\n", ok == (bool)ans ? "John" : "Brother");
}
}
HDU-3595 GG and MM(every-SG)
结论:多线程博弈,所有游戏都无法继续进行时,游戏结束。对于每一个子游戏,如果自己是必败的,那么希望越快结束越好,自己是必胜的,那么希望越慢结束越好。
最后检测最大步数是否为奇数,奇数则必胜,反之必败
#include<bits/stdc++.h>
using namespace std;
int step[1005][1005];
int pn[1005][1005];
int get_step(int x, int y)
{
if (~pn[x][y])
return pn[x][y];
if (x > y) swap(x, y);
int mx = 0, mn = 0x3f3f3f3f;
pn[x][y] = pn[y][x] = false;
for (int i = x; i <= y; i += x)
{
if (!get_step(x, y - i))
{
mx = max(mx, step[x][y - i]);
pn[x][y] = pn[y][x] = true;
}
else
mn = min(mn, step[x][y - i]);
}
if (pn[x][y])
step[x][y] = step[y][x] = mx + 1;
else
step[x][y] = step[y][x] = mn + 1;
return pn[x][y];
}
int main()
{
memset(pn, -1, sizeof(pn));
for (int i = 0; i <= 1000; ++i)
pn[i][0] = pn[0][i] = step[i][0] = step[0][i]=0;
int n, a, b;
while (~scanf("%d", &n))
{
int mx = 0;
while (n--)
{
scanf("%d%d", &a, &b);
get_step(a, b);
mx = max(mx, step[a][b]);
}
if (mx & 1)
printf("MM\n");
else
printf("GG\n");
}
}
POJ-2484 A Funny Game(环形博弈)
结论:假设有\(n\)个石子,能取连续\(1\sim k\)个石子
证明:如果第一步不能取完,那么第二步就可以将环形圈分段为size(假设为\(x\))相同的两部分,由\(sg[x]\oplus sg[x]=0\) 可以看出,这样可以使得下一步必败
#include<cstdio>
#include<cstdio>
bool circle_game(int n, int k)
{
if (k >= n || k == 1 && (n & 1)) return true;
return false;
}
int main()
{
int n;
while (~scanf("%d", &n) && n)
printf("%s\n", circle_game(n, 2) ? "Alice" : "Bob");
}
HDU-3389 Game(阶梯博弈)
结论:
- 对所有奇数层进行\(Nim\)博弈即可
- 偶数层可以移动到除0外的任何层
- 奇数层可以移动石子到任意偶数层(包括第0层)
可以打表出可移动层
| A | B | A | B | A | B | A | B |
|---|---|---|---|---|---|---|---|
| 1 | |5 | 4 | 9 | 6 | 13 | 2、8 | |
| 2 | 1 | 6 | 3 | 10 | 5 | 14 | 1、7、13 |
| 3 | |7 | 2 | 11 | 4、10 | 15 | 6、12 | |
| 4 | |8 | 1、7 | 12 | 3、9 | 16 | 5、11 | |
| 此题通过打表可以发现1、3、4三层是无法挪动的,即有3个第0层 | |||||||
| 层数 | 0 | 1 | 2 | 3 | 4 | 5 | ... |
| - | - | - | - | - | - | - | - |
| 游戏1 | 1 | 2 | 7 | 8 | 13 | 14 | 奇数层为\(2+6i\) |
| 游戏2 | 3 | 6 | 9 | 12 | 15 | 18 | 奇数层为\(6+6i\) |
| 游戏3 | 4 | 5 | 10 | 11 | 16 | 17 | 奇数层为\(5+6i\) |
#include<cstdio>
int main()
{
int T, n, t;
scanf("%d", &T);
for (int cas = 1; cas <= T; ++cas)
{
int sg[3] = { 0 };
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
scanf("%d", &t);
switch (i % 6)
{
case 0:sg[0] ^= t; break;
case 2:sg[1] ^= t; break;
case 5:sg[2] ^= t;
}
}
printf("Case %d: %s\n", cas, sg[0] ^ sg[1] ^ sg[2] ? "Alice" : "Bob");
}
}
luogu-P2252 取石子游戏(威佐夫博弈)
结论:假设两堆各有 \(a\ b\) 个石子
//做题头
#include<bits/stdc++.h>
using namespace std;
int main()
{
int a, b;
scanf("%d%d", &a, &b);
if (a > b)
swap(a, b);
printf("%d",int((sqrt(5) + 1) / 2 * (b - a)) != a);
}
5、K倍动态减法游戏
假设有\(n\)个石子,第一次能取\(1\sim n-1\)个,若每次取\(x\)个,则下一次可以取\(1\sim kx\)个
结论:
- 假设存在一个必败序列\(a[i]\),同时当前必败数列能够造出来的最大的数用\(b[i]\)进行存储,很显然,如果\(b[i]\)由至少两个数,例如\(a[x]\)和\(a[y](x<y\le i)\)构成,那么一定要保证的是\(a[x]<k*a[y]\),只有这样才能使得取走\(a[x]\)时,对方无法取走\(a[y]\),那么优胜策略便是每次取走某个数\(n\)被\(a\)数组分解后最小的\(a\)数组元素对应的值。
- 可以类比\(k=1\)时的特殊情况,将一个数二进制表示后,每次取走最低位对应的数,那么对方一定无法取得次低位的数,也就会产生新的最低位,那么循环往复,自己永远会有数可取,直至自己取空游戏胜利。
- 关于\(a,b\)数组的构建,由于\(b[i]\)表示\(a\)中前\(i\)个元素满足上述规则,所能构建的最大的值,那么\(b[i]+1\)必须要由\(a[i+1]\)来进行表示,以保证每个数都能由若干个\(a\)中元素分解。同时新的\(b[i+1]\)就可以由当前最大的\(a\)中元素\(a[i+1]\),与满足\(a[j]*k<a[i+1]\)的下标\(j\)所对应的\(b[j]\)求和获得。
- 另外值得注意的是,如果构成\(b[i]\)的\(a\)中元素只有1个的情况,即\(i+1\)太小,找不到\(a[j]*k<a[i+1]\)满足条件的\(j\)时,那么直接使得\(b[i+1]=a[i+1]\)即可
struct kmuti_game
{
static const int MAXN = 1e5;
int a[MAXN] = { 1 }, b[MAXN] = { 1 };
void init(int k)
{
int j;
for (int i = 1; i < MAXN; ++i)
{
a[i] = b[i - 1] + 1;
for (j = 0; a[j + 1] * k < a[i]; ++j);
if (a[j] * k < a[i])
b[i] = b[j] + a[i];
else
b[i] = a[i];
}
}
};
6、翻硬币类游戏
结论:
- 当x的二进制1个数为奇数个时,\(sg[x]=2x\)
- 当x的二进制1个数为偶数个时,\(sg[x]=2x+1\)
也不太懂 QAQ 待补

根据学长例会以及刷过的博弈题的总结。分为以下几个板块:PN法、巴什博弈、SG定理、Nim博弈、搜索求SG值、SG必败点变形、anti-SG、every-SG、环形博弈、阶梯博弈、威佐夫博弈
浙公网安备 33010602011771号