Solution -「HNOI 2007」「洛谷 P3185」*游戏

(mathcal{Description})

  Link.

  给定 (n) 堆石子,数量为 ({a_n}),双人博弈,每轮操作选定 (i<jle k),使 (a_i leftarrow a_i-1)(a_j leftarrow a_j+1)(a_k leftarrow a_k+1),并保证操作后所有 (a_ige0)。求保证先手胜的第一步操作方案数和字典序最小的第一步操作。

  多测,(nle21)(0le a_ile10^4),数据组数 (le10)

(mathcal{Solution})

  由于每次只能取走一个石子,所以一个有 (x) 个石子的位置实际上可以看做 (x) 堆互不相关的石子放在同一个位置。而由于“互不相关”,求出每个位置上有一颗石子的 SG 函数异或起来就是答案。令 (operatorname{sg} (i)) 表示位置 (i) 有一颗石子的 SG 值,显然:

[ operatorname{sg} (i)=operatorname{mex}_{i<jle k}{operatorname{sg} (j)oplusoperatorname{sg}(k)} ]

  扫出 (operatorname{sg}),设所有石子 (operatorname{sg}) 异或和为 (X),据此判断是否有解。若有解,暴力枚举第一次操作的 (i,j,k),若 (Xoplus operatorname{sg} (i)oplus operatorname{sg} (j)oplus operatorname{sg} (k)=0),说明操作后先手必败,此次操作计入贡献,最终 (mathcal O(Tn^3)) 就解决啦!

(mathcal{Code})

/* Clearink */

#include <cstdio>
#include <cstring>

const int MAXN = 21;
int n, sg[MAXN + 5], a[MAXN + 5];

inline int calcSG ( const int i ) {
	if ( ~sg[i] ) return sg[i];
	bool vis[105] {};
	for ( int j = i + 1; j <= n; ++ j ) {
		for ( int k = j; k <= n; ++ k ) {
			vis[calcSG ( j ) ^ calcSG ( k )] = true;
		}
	}
	for ( int j = 0; ; ++ j ) if ( !vis[j] ) return sg[i] = j;
}

int main () {
	int T;
	for ( scanf ( "%d", &T ); T --; ) {
		scanf ( "%d", &n ), memset ( sg, 0xff, sizeof sg );
		int ans = 0;
		for ( int i = 1; i <= n; ++ i ) {
			if ( scanf ( "%d", &a[i] ), a[i] & 1 ) {
				ans ^= calcSG ( i );
			}
		}
		if ( !ans ) { puts ( "-1 -1 -1
0" ); continue; }
		int ways = 0;
		for ( int i = 1; i <= n; ++ i ) {
			if ( !a[i] ) continue;
			for ( int j = i + 1; j <= n; ++ j ) {
				for ( int k = j; k <= n; ++ k ) {
					if ( !( ans ^ calcSG ( i ) ^ calcSG ( j ) ^ calcSG ( k ) ) && !ways ++ ) {
						printf ( "%d %d %d
", i - 1, j - 1, k - 1 );
					}
				}
			}
		}
		printf ( "%d
", ways );
	}
	return 0;
}
原文地址:https://www.cnblogs.com/rainybunny/p/13752037.html