【题解】一本通例题 取石子游戏

Link

\(\text{Solution:}\)

由于有两种操作,不太好搞。

观察到,如果不考虑合并,显然总石子数为奇数则先手必胜,否则必败。

对于一个局面,显然我们最多操作次数是\((\sum a_i)+n-1\)

定义\(b=n-1+\sum a_i\),则如果不存在石子数为\(1\)的堆,同样有\(b\)为奇数必胜。

证明:若堆数为\(1\)显然。

\(b\)是奇数,则先手选择合并两堆使得\(b\)为偶数。那么下面证明\(b\)为偶数必败。

若此时合并两堆,则下一次对手也合并两堆,没有用。

如果这时取走一个石子数\(\leq 3\)的堆的石子,则我继续合并,\(b\)奇偶性不变。

若这时取走一个石子数\(=2\)的堆的石子,则我把剩下的一个并到其它石子中,\(b\)奇偶性不变。

证毕。

因为\(n<=50,\)我们可以考虑记忆化搜索。设\(dp[i][j]\)表示石子数为\(1\)的堆数为\(i\),剩下的操作数为\(j\)是否有必胜策略。

那么我们可以从下面四个方面转移:

  • 拿走一个石子数为\(1\)的堆:\(dp[i-1][j]\)

  • 合并两个石子数为\(1\)的堆:\(dp[i-2][j+2+(j>0)]\)

  • 合并一个石子与另一个多的石子:\(dp[i-1][j+1]\)

  • 拿掉一个石子:\(dp[i][j-1]\)

#include<bits/stdc++.h>
using namespace std;
int T,n;
char dp[51][60000];
char dfs(int a,int b){
	if(a==0)return dp[a][b]=(b&1);
	if(b==1)return dp[a][b]=dfs(a+1,0);
	if(~dp[a][b])return dp[a][b];
	char &F=dp[a][b];
	if(a>=2&&!dfs(a-2,b+2+(b?1:0)))return F=true;
	if(b&&!dfs(a,b-1))return F=true;
	if(a&&b&&!dfs(a-1,b+1))return F=true;
	if(a&&!dfs(a-1,b))return F=true;
	return F=false;
}
int main(){
	scanf("%d",&T);
	memset(dp,-1,sizeof(dp));
	while(T--){
		scanf("%d",&n);int b=-1,a=0;
		for(int i=1;i<=n;++i){
			int x;scanf("%d",&x);
			if(x==1)a++;
			else b+=x+1;
		}
		if(b==-1)b=0;
		puts(dfs(a,b)?"YES":"NO");
	}
	return 0;
}
posted @ 2020-06-10 19:52  Refined_heart  阅读(262)  评论(0编辑  收藏  举报