Solution -「HNOI 2007」「洛谷 P3185」分裂游戏
\(\mathcal{Description}\)
Link.
给定 \(n\) 堆石子,数量为 \(\{a_n\}\),双人博弈,每轮操作选定 \(i<j\le k\),使 \(a_i \leftarrow a_i-1\),\(a_j \leftarrow a_j+1\),\(a_k \leftarrow a_k+1\),并保证操作后所有 \(a_i\ge0\)。求保证先手胜的第一步操作方案数和字典序最小的第一步操作。
多测,\(n\le21\),\(0\le a_i\le10^4\),数据组数 \(\le10\)。
\(\mathcal{Solution}\)
由于每次只能取走一个石子,所以一个有 \(x\) 个石子的位置实际上可以看做 \(x\) 堆互不相关的石子放在同一个位置。而由于“互不相关”,求出每个位置上有一颗石子的 SG 函数异或起来就是答案。令 \(\operatorname{sg} (i)\) 表示位置 \(i\) 有一颗石子的 SG 值,显然:
\[ \operatorname{sg} (i)=\operatorname{mex}_{i<j\le k}\{\operatorname{sg} (j)\oplus\operatorname{sg}(k)\}
\]
扫出 \(\operatorname{sg}\),设所有石子 \(\operatorname{sg}\) 异或和为 \(X\),据此判断是否有解。若有解,暴力枚举第一次操作的 \(i,j,k\),若 \(X\oplus \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\n0" ); 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\n", i - 1, j - 1, k - 1 );
}
}
}
}
printf ( "%d\n", ways );
}
return 0;
}