bzoj 3895: 取石子

$ \color{#0066ff}{ 题目描述 }$

Alice和Bob两个好朋含友又开始玩取石子了。游戏开始时,有N堆石子

排成一排,然后他们轮流操作(Alice先手),每次操作时从下面的规则中任选一个:

·从某堆石子中取走一个

·合并任意两堆石子

不能操作的人输。Alice想知道,她是否能有必胜策略。

\(\color{#0066ff}{输入格式}\)

第一行输入T,表示数据组数。

对于每组测试数据,第一行读入N。

接下来N个正整数a1,a2…an,表示每堆石子的数量。

\(\color{#0066ff}{输出格式}\)

对于每组测试数据,输出一行。

输出YES表示Alice有必胜策略,输出NO表示Alice没有必胜策略。

\(\color{#0066ff}{输入样例}\)

3
3
1 1 2
2
3 4
3
2 3 5

\(\color{#0066ff}{输出样例}\)

YES
NO
NO

\(\color{#0066ff}{数据范围与提示}\)

100%的数据满足T<=100, N<=50. ai<=1000

\(\color{#0066ff}{题解}\)

如果合并,石子数不变,如果取,石子数-1,表面上看,结局已经注定,跟操作数的奇偶有关

然而,会有这样的情况,比如1和1

合并,那么操作数-1,如果取走一个1,那么操作数-2!!!这是不一样的

于是我们发现,状态实际上只跟1的堆数和操作数有关

所以,设\(sg[x][y]\)表示当前有x堆是1,除了1的那些堆的操作数共y次的SG值

然后分别讨论所有情况进行转移

因为不涉及多个游戏的合并,所以sg不是0就是1,就不用开vis数组记录什么东西了

#include<bits/stdc++.h>
#define LL long long
LL in() {
	char ch; LL x = 0, f = 1;
	while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
	return x * f;
}
int n;
int sg[55][52050];
int work(int x, int y) {
	if(!x) return y & 1;
	if(y == 1) return sg[x][y] = work(x + 1, y - 1);
	if(~sg[x][y]) return sg[x][y];
	if(x && !work(x - 1, y)) return sg[x][y] = 1;
	if(x && y && !work(x - 1, y + 1)) return sg[x][y] = 1;
	if(x >= 2 && !work(x - 2, y + 2 + (y > 0))) return sg[x][y] = 1;	
	if(y && !work(x, y - 1)) return sg[x][y] = 1;
	//拿走一个1
	//1跟非1合并
	//两个1合并
	//某个非1堆拿走1个或合并两个非1堆
	return sg[x][y] = 0;
}
int main() {
	memset(sg, -1, sizeof sg);
	for(int T = in(); T --> 0;) {
		n = in();
		int x = 0, y = 0, z;
		for(int i = 1; i <= n; i++) {
			z = in();
			x += (z == 1);
			y += (z > 1) * z;
		}
		y += n - x - 1;
		if(y == -1) y++;
		printf(!work(x, y)? "NO\n" : "YES\n");
	}
	return 0;
}
posted @ 2019-03-03 14:47  olinr  阅读(177)  评论(0编辑  收藏  举报