【题解】「CEOI2021」Stones
\(N\) 堆石子,每次由对手选择一堆,然后自己从对手选的一堆中拿走若干个石子,轮流操作,拿走最后一颗石子即为胜利,求必胜策略。
看起来非常像 Nim 游戏,首先考虑 SG 函数与异或和之类的。
想了半天发现自己方向错了,这完全是个发扬人类智慧的博弈题。
仔细观察一下发现每次选择一堆石子,要么取完,要么剩一个子。如果剩更多的子,再取一次和一次取完本质上没有区别。
所以如果除了当前被指定的一堆外,还剩偶数堆棋子必胜,否则必败。
如果当前堆只有 \(1\) 粒石子,那么取完这粒石子,给对手也指定一堆只有 \(1\) 粒石子的情况。
否则如果这是最后一堆大于 \(1\) 的堆,那么就取完,否则就留下 \(1\) 粒让对手取。
这样,如果对手让我们取大于 \(1\) 的堆,我们就将这一堆消耗完,否则就消耗两堆 \(1\)。相当于把最后一堆大于 \(1\) 的堆当作 \(1\) 来处理,这样一个回合后,\(1\) 的堆奇偶性不变,其余堆只会减少。并且每次对手只能取大小为 \(1\) 的堆。这样局面就完全被自己掌控了。
时间复杂度 \(\mathcal{O}(N^2)\)。
#define N 505
int n, a[N], cnt;
bool ck(){
rp(i, n)if(a[i] == 1){
printf("%d\n", i); fflush(stdout), a[i] = 0;
int x; read(x); return true;
}
return false;
}
int main() {
read(n);
rp(i, n)read(a[i]), cnt += a[i] > 1;
while(true){
int x; read(x);
if(a[x] == 1){
printf("1\n"), fflush(stdout), a[x] = 0;
if(!ck())break;
}
else{
if(1 == cnt){
printf("%d\n", a[x]), fflush(stdout), a[x] = 0;
if(!ck())break;
}
else{
printf("%d\n", a[x] - 1), fflush(stdout), a[x] = 0;
printf("%d\n", x), fflush(stdout);
read(x), cnt--;
}
}
}
puts("-1"); fflush(stdout);
return 0;
}