[BZOJ 1188] [HNOI2007] 分裂游戏 【博弈论|SG函数】
题目链接:BZOJ - 1188
题目分析
我们把每一颗石子看做一个单个的游戏,它的 SG 值取决于它的位置。
对于一颗在 i 位置的石子,根据游戏规则,它的后继状态就是枚举符合条件的 j, k。然后后继状态就是 j 与 k 这两个游戏的和。
游戏的和的 SG 值就是几个单一游戏的 SG 值的异或和。
那么还是根据 SG 函数的定义 , 即 SG(u) = mex(SG(v)) ,预处理求出每个位置的 SG 值。一个位置的 SG 值与它后面的位置有关,是取决于它是倒数第几个位置,那么我们预处理求出的 SG[i] 是指倒数第 i 个位置的 SG 值。
还有一个十分重要的性质,我们不需要考虑每个位置上石子的数量,只需要考虑数量的奇偶,因为如果有偶数个石子,那么这个位置的 SG 值会被异或到整个状态的 SG 中共偶数次,就会抵消,相当于没有。
奇数就是相当于偶数 + 1,因此只要异或一次即可。
组合游戏真是有许多神奇的性质Orz。
代码
#include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> #include <cmath> using namespace std; const int MaxN = 25, N = 21; int T, n, Mark_Index; int A[MaxN], SG[MaxN], Mark[MaxN * MaxN]; void Prepare_SG() { Mark_Index = 0; memset(Mark, 0, sizeof(Mark)); for (int i = 1; i <= N; ++i) { ++Mark_Index; for (int j = i - 1; j >= 1; --j) for (int k = j; k >= 1; --k) Mark[SG[j] ^ SG[k]] = Mark_Index; for (int j = 0; j <= N * N; ++j) { if (Mark[j] != Mark_Index) { SG[i] = j; break; } } } } int main() { scanf("%d", &T); Prepare_SG(); for (int Case = 1; Case <= T; ++Case) { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", &A[i]); int Temp = 0, Tot = 0; for (int i = 1; i <= n; ++i) if (A[i] & 1) Temp ^= SG[n - i + 1]; for (int i = 1; i <= n; ++i) { if (A[i] == 0) continue; for (int j = i + 1; j <= n; ++j) { for (int k = j; k <= n; ++k) { if ((Temp ^ SG[n - i + 1] ^ SG[n - j + 1] ^ SG[n - k + 1]) != 0) continue; ++Tot; if (Tot == 1) printf("%d %d %d\n", i - 1, j - 1, k - 1); } } } if (Tot == 0) printf("-1 -1 -1\n"); printf("%d\n", Tot); } return 0; }