公平组合游戏:
1.由两名玩家交替行动;
2.在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关;
3.不能行动的玩家判负;
有向图游戏:给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。
任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。
\(nim\) 游戏属于公平组合游戏。
在有向图游戏中,对于每个节点 \(x\),设从 \(x\) 出发共有 \(k\) 条有向边,分别到达节点 \(y_1, y_2, ..., y_k\),定义 \(SG(x)\) 为 \(x\) 的后继节点 \(y_1, y_2, ..., y_k\) 的 \(SG\) 函数值构成的集合再执行 \(mex(S)\) 运算的结果。
即:\(SG(x) = mex({SG(y_1), SG(y_2), ..., SG(y_k)})\)
nim游戏
lugou:https://www.luogu.com.cn/problem/P2197
acwing:https://www.acwing.com/problem/content/893/
\(n\) 堆石子,两人依次从任意一堆中取任意个,不能操作的人输。问先手是否必胜。
当 \(a_1\) ^ \(a_2\) ^ ... ^ \(a_n\) != 0 时,先手必胜。
#include <bits/stdc++.h>
using namespace std;
int T, n;
void solve(){
cin >> n;
int k = 0, x;
for (int i = 1; i <= n; i ++ ){
cin >> x;
k ^= x;
}
if (k) cout << "Yes\n";
else cout << "No\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while ( T -- )
solve();
return 0;
}
SG函数
\(SG\) 函数异或值大于 0 则先手必胜。
acwing:https://www.acwing.com/problem/content/895/
\(n\) 堆石子以及一个 \(k\) 个不同整数组成的数字集合 \(S\)。两个玩家依次操作,每次从任意一堆石子中取集合中一个数个数的石子,不能操作的人输。问先手是否必胜。
#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = 1e4 + 10;
int n, m, x, ans, s[N], f[M];
int sg(int x){
if (f[x] != -1) return f[x];
unordered_set <int> S;
for (int i = 0; i < m; 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 >> m;
for (int i = 0; i < m; i ++ )
cin >> s[i];
memset(f, -1, sizeof f);
cin >> n;
for (int i = 0; i < n; i ++ ){
cin >> x;
ans ^= sg(x);
}
if (ans) cout << "Yes\n";
else cout << "No\n";
return 0;
}
acwing:https://www.acwing.com/problem/content/896/
\(n\) 堆石子,两个玩家依次取走一堆石子,再放入两堆数量更小的石子,不能操作的人输。问先手是否必胜。
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int ans, n, f[N], x;
int sg(int x){
if (f[x] != -1) return f[x];
unordered_set<int> S;
for (int i = 0; i < x; i ++ )
for (int j = 0; j <= i; j ++ )
S.insert(sg(i) ^ sg(j));
for (int i = 0; ; i ++ )
if (!S.count(i))
return f[x] = i;
}
int main(){
cin >> n;
memset(f, -1, sizeof f);
for (int i = 0; i < n; i ++ ){
cin >> x;
ans ^= sg(x);
}
if (ans) cout << "Yes\n";
else cout << "No\n";
return 0;
}