题解:博弈+记忆化搜索
刚做这个题的时候想写小数据的对抗搜索,但是失败了。
我们可以先考虑所有的X>1的情况,当只有一堆石子的时候操作上=x,若x为奇数则先手必胜,否则必败。
在考虑有多堆的时候,操作数tot=sum(x)+堆数-1,此时如果tot为奇数则先手必胜,否则必败。
为什么呢?如果不考虑合并操作,那么就相当于有sum(x)个石子每次都只能取一个,同一堆时的情况相同,因为有合并操作,合并操作其实就是可以把必败态推给对手,最多合并堆数-1次,推偶数次相当于没推。
下面证明堆数不为1时,当tot为偶数并且不存在石子数量为1的堆时,先手必败.
1.如果先手选择合并两堆石子,那么每堆石子的个数依然大于1,tot变为奇数.
2.如果先手选择从一堆石子数大于2的堆中拿走一枚石子,那么同上每堆石子个数依然大于1,tot变为奇数。
3.如果先手选择从一堆石子数等于2的堆中拿走一枚石子,那么后手可以合并剩下的1枚石子到任意一个堆(堆数-1,石子数,先手依旧面临偶数)。那样tot的奇偶性不变,每堆石子的个数依然大于1. 那么为什么后手不会选择把剩下的一个取走呢?因为取走后堆数-1,石子数-1,这样奇偶性就变了,就把必胜太留给了对手,博弈的两个人一定是聪明的所以他不可能这么选择)
那么存在x=1的堆怎么办呢?我们可以分开考虑。
假设x=1的堆的个数为a,剩下堆的操作数为b ,我们用f[a][b]表示当前状态是否必胜。
每次枚举所有的操作进行转移:
1.将两堆x=1合并成一堆,这样a-2,b+2+(b?1:0)如果原本b=0,说明只有刚合并的这一堆,其他的没法合并
2.从x=1的堆中取走一个石子,a-1
3.从x=1的堆中取一个石子合并到后面x!=1的任意堆中,注意此时的把b!=0
4从x!=1的堆中任意取一个
这样进行记忆化搜索即可。
#include
#include
#include
#include
#include
using namespace std;
int n,m,t;
int f[100][100000];
int dfs(int a,int b)
{
if (f[a][b]!=-1) return f[a][b];
if (a==0) return f[a][b]=b%2;
if (b==1) return f[a][b]=dfs(a+1,0);
int t=2;
if (a) t=min(t,dfs(a-1,b));
if (a>1) t=min(t,dfs(a-2,b+2+(b?1:0)));
if (a&&b) t=min(t,dfs(a-1,b+1));
if (b) t=min(t,dfs(a,b-1));
if (!t) f[a][b]=1;
else f[a][b]=0;
return f[a][b];
}
int main()
{
scanf("%d",&t);
memset(f,-1,sizeof(f));
for (int T=1;T<=t;T++)
{
scanf("%d",&n);
int a=0,b=0;
for (int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
if (x==1) a++;
else b+=x+1;
}
if (b>0) b--;
if (dfs(a,b)==1) printf("YES\n");
else printf("NO\n");
}
}