Loading

【题解】「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;
}
posted @ 2022-03-15 20:36  7KByte  阅读(142)  评论(0编辑  收藏  举报