博弈论
巴什博弈Bash
1堆n个石子,每次最少取一个,最多取m个
例如 m = 4 判断此刻先手状态(1为胜,0为败) n = 0, 0 n = 1, 1 n = 2, 1 n = 3, 1 n = 4, 1 n = 5, 0 n = 6, 1 n = 7, 1 n = 8, 1 n = 9, 1 n = 10, 0
必败态:当 n % (m + 1) == 0
尼姆博弈Nim
Nim
有n堆石子,每堆石子的数量分别是a1,a2,a3……,两个人依次从这些石子堆中选一堆,拿取任意的石子,至少一个,最后一个拿光石子的人胜利
必败态:每堆石子异或结果为0。
P2197 【模板】nim 游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
void solve() { int n; cin >> n; int ans = 0; for (int i = 0, x; i < n; i ++) { cin >> x; ans ^= x; } cout << ((ans) ? "Yes\n" : "No\n"); }
void solve() { int n, k; cin >> n >> k; int ans = 0, t; // 判断能否减少第 k 位的数字 使得异或和为零 for (int i = 1, x; i <= n; i ++) { cin >> x; if (i == k) t = x; else ans ^= x; } // 至少要拿一颗石子 所以要严格大于异或和ans cout << ((ans < t) ? "Yes\n" : "No\n"); }
void solve() { int n, q; cin >> n >> q; ll ans = 0; vector <ll> a(n + 1); for (int i = 1; i <= n; i ++) { cin >> a[i]; ans ^= a[i]; } ll x, y; while (q --) { cin >> x >> y; ans ^= a[x]; a[x] = y; ans ^= y; if (ans) cout << "Kan\n"; else cout << "Li\n"; } }
#include <bits/stdc++.h> using namespace std; #define ll long long #define qwq ios::sync_with_stdio(0), cin.tie(0), cout.tie(0) const int N = 2e5 + 10, P = 998244353, INF = 0x3f3f3f3f; int sg[110][110]; void getSG(int x, int y) { if (x >= 1 && !sg[x - 1][y]) sg[x][y] = 1; if (x >= 3 && !sg[x - 3][y]) sg[x][y] = 1; if (x >= 9 && !sg[x - 9][y]) sg[x][y] = 1; if (y >= 1 && !sg[x][y - 1]) sg[x][y] = 1; if (y >= 3 && !sg[x][y - 3]) sg[x][y] = 1; if (y >= 9 && !sg[x][y - 9]) sg[x][y] = 1; } void solve() { int n,m; cin >> n >> m; sg[0][0] = 0; for (int i = 0; i <= 100; i ++) for (int j = 0; j <= 100; j ++) getSG(i, j); cout << ((sg[n][m]) ? "win\n" : "lose\n"); } int main() { qwq; int T = 1; // cin >> T; while (T--) solve(); return 0; }
对于偶数台阶 想要移动到地面需要进行偶数次操作
每次拿x个石子到奇数台阶后 对方可以再拿x个至偶数台阶
所以偶数台阶对题目无影响
对于奇数台阶 所有奇数台阶满足经典Nim游戏
异或和为0时 先手必败
void solve() { int n, ans = 0; cin >> n; for (int i = 1, x; i <= n; i ++) { cin >> x; if (i & 1) ans ^= x; } if (ans) puts("Yes"); else puts("No"); }
Nimk
有n堆石子,每堆石子的数量分别是a1,a2,a3……,两个人依次从这些石子堆中选 [1, k ] 堆,拿取任意的石子,至少一个,最后一个拿光石子的人胜利
必败态: 把所有数字(每堆石子的个数)转化成二进制后, 求每一位上1的个数num_i, 每一位都满足 num_i % (k + 1) == 0
sg函数
对于n个子游戏(比如n个都操作不了才算输)
我们可以用一个数字去表示他的状态 状态之间可以转移,就像nim游戏中a[i]个石头可以取走1~a[i]个
在nim游戏中 这样操作的a[i]可以变成0~a[i]-1中的任意情况 而这个数字 sg 就可以去表示它
对于任意状态 x , 定义 SG(x) = mex(S),其中 S 是 x 后继状态的SG函数值的集合。如 x 有三个后继状态分别为 SG(a),SG(b),SG(c),那么SG(x) = mex{SG(a),SG(b),SG(c)}
SG(x)=0代表x为必败态,SG(x)!=0代表x为必胜态
求sg函数
int getSG(int x) { if (sg[x] != -1) return sg[x]; unordered_set<int> st; for (int i = 0; i < k; i ++) if (x >= a[i]) st.insert(getSG(x - a[i])); // 后继状态 for (int i = 0; ; i ++) if (!st.count(i)) return sg[x] = i; // 求mex }
打表方式
for (int i = 0; i <= 100; i ++) for (int j = 0; j <= 100; j ++) printf("sg[%d][%d] = %d\n", i, j, sg[i][j]);
for (int i = 0; i <= 100; i ++) cout << setw(3) << i << " \n"[i == 100]; for (int i = 0; i <= 100; i ++) cout << setw(3) << sg[i] << " \n"[i == 100];
for (int i = 1; i <= n; i ++) cout << setw(2) << i << " \n"[i == n]; for (int i = 1; i <= n; i ++) { cout << setw(2) << i << " "; for (int j = 1; j <= n; j ++) cout << setw(2) << sg[i][j] << " \n"[j == n];