首页 > 代码库 > [BZOJ 1188] [HNOI2007] 分裂游戏 【博弈论|SG函数】

[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;}

  

[BZOJ 1188] [HNOI2007] 分裂游戏 【博弈论|SG函数】