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