【题解】#10246. 「一本通 6.7 练习 3」取石子

Description

Sample Input

3c++

3spa

1 1 2code

2blog

3 4ip

3input

2 3 5it

Sample Output

YESio

NOclass

NOim

Solution

我天,真神仙题!

这题实际上是博弈论DP,一开始还想着直接用SG * 过去。

咱们先从最简单的入手:

只有一堆石子时咱们能够不考虑合并形成的影响那么一我的赢的状况只有多是他剩下能够进行的操做数是奇数。

(这里咱们发现剩下能够进行的操做数只有取一个石子)

那若是有两个堆。

(假设只有一个石子的堆叫作寂寞堆,大于一个石子的堆叫作热闹堆)

那么咱们要分两种状况分别讨论:

\(1.\) 咱们有两个堆,一个寂寞堆一个热闹堆。

那么咱们假设寂寞堆 \(1\) 个石子,热闹堆 \(2\) 个石子,那么很明显咱们当前只有要么从两个堆里取一个,要么合并。

  • 首先考虑合并:合并以后热闹堆的奇偶性变了,同时合并以后取的是对手,这样就保证了对手赢。

  • 考虑先把寂寞堆取完,那么咱们在热闹堆中是能够直接根据奇偶求出谁会赢。若是先取热闹堆对手是有赢的策略的。

到这里咱们发现好像寂寞堆会影响答案,若是只有热闹堆,热闹堆之间的合并不会改变他们的奇偶,对咱们考虑没有影响,只会致使赢输的人不同。

但若是出现了寂寞堆,寂寞堆的合并会影响热闹堆的奇偶性,因此要特殊考虑寂寞堆。

这个时候就要咱们上博弈论DP了 然而我不知道为何要上(逃

设状态 \(f[i][j]\) 表示有 \(i\) 个寂寞堆,\(j\) 次对于热闹堆的操做时当前操做的人是赢仍是输。

这个状态好诡异

咱们转移怎么办呢?

分类讨论一下:

\(1.\) 寂寞堆操做

  • 寂寞堆取一个石子,很明显转移到 \(f[i-1][j]\)
  • 寂寞堆合并(2个寂寞堆) 转移到 \(f[i-2][j+2]\)(合并以后多了一个热闹堆,要对热闹堆进行两次取石子操做),但忽然发现若是还有热闹堆的话咱们还会多一次合并操做,那就转移到 \(f[i-2][j+2+(j?1:0)]\)
  • 寂寞堆合并到热闹堆上,转移到 \(f[i-1][j+1]\)

\(2.\) 热闹堆操做

  • 从热闹堆里取一个石子,须要考虑是否取了以后变为寂寞堆。也就是 \(j\) 是否为 \(1\)。两个转移 \(f[i+1][0](j==1),f[i+1][j-1]\)

这样以后好像就没啥子了。注意一下细节就莫得了。

#include<bits/stdc++.h>
using namespace std;

int T,n;
const int N=55,M=1005;
int a[N],f[N][M*N];

inline int dfs(int num,int sum){
	if(num<=0 && sum<=0) return 0;
	if(f[num][sum]!=-1) return f[num][sum];
	if(num<=0) return f[num][sum]=(sum&1);
	if(sum==1) return f[num][sum]=dfs(num+1,0);
	f[num][sum]=0;
	if(num && !dfs(num-1,sum)) return f[num][sum]=1;					// 拿一个寂寞堆的石子
	if(sum && !dfs(num,sum-1)) return f[num][sum]=1;    				// 把一个热闹堆里拿掉一个石子
	if(num && sum && !dfs(num-1,sum+1)) return f[num][sum]=1;			// 把一个寂寞堆合并到热闹堆上
	if(num>1 && !dfs(num-2,sum+2+(sum?1:0))) return f[num][sum]=1;		// 把两个寂寞堆合并
	return f[num][sum];
}

int main(){
	scanf("%d",&T);
	memset(f,-1,sizeof(f));
	while(T--){
		int cnt=0,step=0;
		scanf("%d",&n);
		for(int i=1;i<=n;++i){
			scanf("%d",&a[i]);
			if(a[i]==1) cnt++;
			if(a[i]>1)  step+=a[i]+1;
		}
		if(step) step--;
		printf("%s\n",dfs(cnt,step)?"YES":"NO");	
	}
	return 0;
}
相关文章
相关标签/搜索