在博弈论中,有这样一类问题:公平组合游戏(Impartial Combinatorial Games,ICG)。
若一个游戏满足:
- 由两名玩家交替行动;
- 游戏中任意时刻,合法操作集合只取决于这个局面本身;
- 若轮到某位选手时,若该选手无合法操作,则这名选手判负;
则称该游戏为一个公平组合游戏。
先来说两个概念:必胜态和必败态:
- 必胜态:若双方都采取最佳策略,那么持有当前状态的人必胜,当前状态则为必胜态。
- 必败态:若双方都采取最佳策略,那么持有当前状态的人必败,当前状态则为必胜态;
如果将每个状态表示为一个节点,那么能走到必败态的一定是必胜态,走不到任何必败态的一定是必败态。
Nim游戏
ICG中一种叫做Nim游戏,它是最简单的且最常见的博弈论问题,学习博弈论的话首先要掌握这个问题。
Nim游戏的规则是:有
n
n
n堆石子,第
i
i
i堆石子有
a
i
a_i
ai个,每次可以从某一堆中取走若干个,先后手轮流取,最后无石子可取的人判负。
对于这个游戏,如果双方都采用最佳策略的话,那么从开局就已经决定胜负了。现在要讨论的是,在每一步都是最佳策略的情况下,先手的人是必败还是必胜。
先给出结论:若 a 1 ⊕ a 2 ⊕ … ⊕ a n = 0 a_1⊕a_2⊕…⊕a_n=0 a1⊕a2⊕…⊕an=0,那么先手必败;否则先手必胜。
下面给出一个简单的证明:
- 我们已知, a 1 = a 2 = … = a n = 0 a_1=a_2=…=a_n=0 a1=a2=…=an=0为必败态,此时 0 ⊕ 0 ⊕ … ⊕ 0 = 0 0⊕0⊕…⊕0=0 0⊕0⊕…⊕0=0;
- 若
a
1
⊕
a
2
⊕
…
⊕
a
n
=
x
≠
0
a_1⊕a_2⊕…⊕a_n=x ≠0
a1⊕a2⊕…⊕an=x=0,证明:一定可以通过某种方式让
a
1
⊕
a
2
⊕
…
⊕
a
n
=
0
a_1⊕a_2⊕…⊕a_n=0
a1⊕a2⊕…⊕an=0;
设 x x x的二进制表示中最高的一位1是第 k k k位;
那么说明 a 1 , a 2 … a n a_1,a_2…a_n a1,a2…an中至少存在一个数第 k k k位是1;
不难得出 a i ⊕ x < a i a_i⊕x<a_i ai⊕x<ai,那么此时从第 i i i堆石子中取走 a i − ( a i ⊕ x ) a_i-(a_i⊕x) ai−(ai⊕x)个,还剩下 a i ⊕ x a_i⊕x ai⊕x个,即 a i ′ = a i ⊕ x a_i'=a_i⊕x ai′=ai⊕x;
此时 a 1 ⊕ a 2 ⊕ … ⊕ a i ′ ⊕ a i + 1 ⊕ … ⊕ a n = a 1 ⊕ a 2 ⊕ … ⊕ a i ⊕ x ⊕ a i + 1 ⊕ … ⊕ a n = x ⊕ x = 0 a_1⊕a_2⊕…⊕a_i'⊕a_{i+1}⊕…⊕a_n=a_1⊕a_2⊕…⊕a_i⊕x⊕a_{i+1}⊕…⊕a_n=x⊕x=0 a1⊕a2⊕…⊕ai′⊕ai+1⊕…⊕an=a1⊕a2⊕…⊕ai⊕x⊕ai+1⊕…⊕an=x⊕x=0;
得证; - 若
a
1
⊕
a
2
⊕
…
⊕
a
n
=
0
a_1⊕a_2⊕…⊕a_n=0
a1⊕a2⊕…⊕an=0(1),证明:不管怎样拿,剩下的异或起来一定不为0;
不妨设在第 i i i堆拿,此时还剩下 a i ′ a_i' ai′个;
假设 a 1 ⊕ a 2 ⊕ … ⊕ a i ′ ⊕ a i + 1 ⊕ … ⊕ a n = 0 a_1⊕a_2⊕…⊕a_i'⊕a_{i+1}⊕…⊕a_n=0 a1⊕a2⊕…⊕ai′⊕ai+1⊕…⊕an=0(2),那么(2)⊕(1)得:
a i ⊕ a i ′ = 0 a_i⊕a_i'=0 ai⊕ai′=0,但 a i ≠ a i ′ a_i≠a_i' ai=ai′,矛盾,因此原结论成立。 - 如果先手的人在开局时面对一个非零状态,那么它一定可以让状态变为零,此时后手的任何操作都会让状态变为非零……以此类推,先手的人永远可以出于非零态,而后手的人一定永远处于零态,再加上游戏进行到最后一定会结束,所以 0 ⊕ 0 ⊕ … ⊕ 0 = 0 0⊕0⊕…⊕0=0 0⊕0⊕…⊕0=0的状态一定会被后手的人遇到,那么先手的人一定会赢;否则先手的人一定会输。
SG函数
先给出一些前置知识:
- Mex运算:设S表示一个非负整数集合。 定义 mex(S) 为求出不属于集合 S 的最小非负整数的运算,即: mex(S) = min{x}, x 属于自然数,且 x 不属于 S 也就是集合里面不存在的最小的自然数,比如 mex{1, 2, 3} = 0;
- SG函数:对于一个有向无环图(Directed Acyclic Graph,DAG),对于其终点(即没有任何出度的点)的SG值定义为0,对于某点 x x x,设它指向的节点为 y 1 , y 2 , … , y k y_1,y_2,…,y_k y1,y2,…,yk,那么 S G ( x ) = M e x { S G ( y 1 ) , S G ( y 2 ) , … , S G ( y k ) } SG(x)=Mex\{SG(y_1),SG(y_2),…,SG(y_k)\} SG(x)=Mex{SG(y1),SG(y2),…,SG(yk)}。
现在把ICG游戏的么每个状态抽象为图中的一个节点,每次操作后都可以从当前节点转移到下一个节点。每个状态都有一个对应的SG值,而终点(也即无法继续进行游戏的状态)的SG值设置为0,然后就可以根据公式 S G ( x ) = M e x { S G ( y 1 ) , S G ( y 2 ) , … , S G ( y k ) } SG(x)=Mex\{SG(y_1),SG(y_2),…,SG(y_k)\} SG(x)=Mex{SG(y1),SG(y2),…,SG(yk)}来计算每一个点的SG值了。这里可以递归处理节点,把每个节点指向的点的SG值存到一个集合set中,再用mex运算求解当前节点的SG值即可。这样就可以得到初始状态的SG值了。
这里给出一道例题来具体演示一下:
原题acwing 集合-Nim游戏
给定 n 堆石子以及一个由 k 个不同正整数构成的数字集合 S。
现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 S,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数 k,表示数字集合 S 中数字的个数。
第二行包含 k 个整数,其中第 i 个整数表示数字集合 S 中的第 i 个数 s i s_i si。
第三行包含整数 n。
第四行包含 n 个整数,其中第 i 个整数表示第 i 堆石子的数量 h i h_i hi。
输出格式
如果先手方必胜,则输出 Yes。
否则,输出 No。
数据范围
1≤n,k≤100,
1≤ s i , h i s_i,h_i si,hi≤10000
输入样例:
2
2 5
3
2 4 7
输出样例:
Yes
先给出结论:把所有石头堆初始的SG值异或起来,若不为0则先手必胜,否则先手必败。
举个例子:假设有一堆石子,有10个,每次可以拿3个或5个,那么做出游戏状态转化图:
对于单个游戏图,可以看到,任何一个SG非零的节点都能转移到一个SG为零的节点。因此循环往复下去,在进行最佳决策的情况下,处于SG为零状态的人一定会输。
对于多个图的证明,同Nim游戏的证明思路完全一样,这里就交给大家思考了。
注意这里的递归要用记忆化搜索来进行。因为对于两堆有相同的石子数量,其SG值是一样的。用记忆化搜索可以大大提高效率。下面给出代码:
#include <iostream>
#include <unordered_set>
#include <cstring>
using namespace std;
const int N = 110, M = 10010;
int n, m;
int s[N], f[M];
int sg(int x)
{
if (f[x] != -1) return f[x];
unordered_set<int> S;
for (int i = 0; i < n; i ++ )
{
int sum = s[i];
if (x >= sum) S.insert(sg(x - sum));
}
for (int i = 0; ; i ++ )
if (!S.count(i))
return f[x] = i;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> s[i];
cin >> m;
memset(f, -1, sizeof f);
int res = 0;
for (int i = 0; i < m; i ++ )
{
int x;
cin >> x;
res ^= sg(x);
}
if (res) puts("Yes");
else puts("No");
return 0;
}