[BZOJ 3895]取石子---博弈+石子合并

该博客探讨了一种取石子的博弈问题,其中每次操作可以合并石子堆或从堆中取走石子。当只剩单一石子的堆和多个石子的堆时,博弈的关键在于合并步数加上石子总数等于初始堆的总和减一。博主通过分析提出将多个石子视为大堆,并利用记忆化搜索解决这个问题。

题意:有n堆石头,可以把多堆合并成一堆,也可以从其中一堆取走一个石子



如果没有个数为1的堆,那么一次肯定取不完一个堆,这个堆一定会被合并

所以能合并的都一定会被合并


所以剩下的一定会是只有大小为1的堆和一些多余1的堆


我们把个数多余1的堆看做新的一个大堆,堆里面石子的个数代表的是它的操作步数

因为每次只能取走一个石子

即合并的步数+石子的个数=∑ai + n - 1

下面我们就要考虑大小为1的堆的个数,因为每一个1都是等价的,就可以记忆化搜索了


//---------------------------------------------------//

其中的操作包括:

把某堆只有一个的,取走

把不是一个的,取走一个

把两堆只有一个的,合并

把某堆只有一个的,合并给不是一个的

采用记忆化搜索




#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int sg[51][50005], n;

//i表示1个的堆,j表示多个的堆

int getsg(int i, int j){
	if(~sg[i][j])return sg[i][j];
	if(j == 1)
	    return sg[i][j] = getsg(i + 1, 0);
	sg[i][j] = 0;
	//在i中取走1个
	if(i && !getsg(i-1, j))
	    sg[i][j] = 1;
	//在j中取走1个
	else if(j && !getsg(i, j-1))
		sg[i][j] = 1;
	//把i中的1个合并到j中
	else if(i && j && !getsg(i-1, j+1))
	    sg[i][j] = 1;
	//把i中的2个合并
	else if(i > 1 && ((j == 0 && !getsg(i-2, j+2)) || (j && !getsg(i-2, j+3))))
	    sg[i][j] = 1;
	return sg[i][j];
}


int main(){
	int test;
	memset(sg, -1, sizeof sg);
	scanf("%d", &test);
	while(test --){
		scanf("%d", &n);
		int x, one = 0, sum = 0;
		for(int i = 1; i <= n; i ++){
			scanf("%d", &x);
			if(x == 1)
			    one += x;
			else
			    sum += x + 1;
		}
		if(sum)
		    sum --;
		puts(getsg(one, sum) ? "YES" : "NO");
	}

	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值