【bzoj 3895】取石子(博弈+记忆化搜索)
3895: 取石子
Time Limit: 1 Sec Memory Limit: 512 MBSubmit: 267 Solved: 130
[Submit][Status][Discuss]
Description
Alice和Bob两个好朋含友又开始玩取石子了。游戏开始时,有N堆石子
排成一排,然后他们轮流操作(Alice先手),每次操作时从下面的规则中任选一个:
·从某堆石子中取走一个
·合并任意两堆石子
不能操作的人输。Alice想知道,她是否能有必胜策略。
Input
第一行输入T,表示数据组数。
对于每组测试数据,第一行读入N。
接下来N个正整数a1,a2…an,表示每堆石子的数量。
Output
对于每组测试数据,输出一行。
输出YES表示Alice有必胜策略,输出NO表示Alice没有必胜策略。
Sample Input
2
3
1 1 2
2
3 4
3
2 3 5
3
1 1 2
2
3 4
3
2 3 5
Sample Output
YES
NO
NO
NO
NO
HINT
100%的数据满足T<=100, N<=50. ai<=1000
Source
【题解】【博弈+记忆化搜索】
【 假设每堆石子的数量都>1,那么我们定义操作数b为当前石子总数+当前堆数-1,若b为奇数,则先手必胜,否则后手必胜】
【1.如果先手选择合并两堆石子,那么每堆石子的个数依然大于1,tot变为奇数.】
【2.如果先手选择从一堆石子数大于2的堆中拿走一枚石子,那么同上每堆石子个数依然大于1,tot变为奇数。】
【3.如果先手选择从一堆石子数等于2的堆中拿走一枚石子,那么后手可以合并剩下的1枚石子到任意一个堆(堆数-1,石子数,先手依旧面临偶数)。那样tot的奇偶性不变,每堆石子的个数依然大于1. 那么为什么后手不会选择把剩下的一个取走呢?因为取走后堆数-1,石子数-1,这样奇偶性就变了,就把必胜太留给了对手,博弈的两个人一定是聪明的所以他不可能这么选择)】
【下面考虑含有1的情况:】
[1) 从石子数为1的堆里取一个;
2) 将两堆石子数为1的堆合并;
3) 把石子数为1的堆与一个石子数不为1的堆合并 ;
4) 从石子数不为1的堆里取走一个]
【共四种情况,记忆化搜索。不过要记住一点:每组数据间不重置】
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,t;
int f[110][100010];
int dfs(int a,int b)
{
if (f[a][b]!=-1) return f[a][b];
if(!a) return f[a][b]=b%2;//如果不存在石子数为1的堆,那么操作数为奇数,先手必胜;反之,先手必败
if(b==1) return f[a][b]=dfs(a+1,0);
int t=2;
if(a) t=min(t,dfs(a-1,b));//从石子数为1的堆里取一个
if(a>1) t=min(t,dfs(a-2,b+2+(b?1:0)));//将两堆石子数为1的堆合并
if(a&&b) t=min(t,dfs(a-1,b+1));//把石子数为1的堆与一个石子数不为1的堆合并
if(b) t=min(t,dfs(a,b-1));//从石子数不为1的堆里取走一个
if(!t) f[a][b]=1;
else f[a][b]=0;
return f[a][b];
}
int main()
{
int i,j;
scanf("%d",&t);
memset(f,-1,sizeof(f));
for(i=1;i<=t;++i)
{
scanf("%d",&n);
int a=0,b=0;
for(j=1;j<=n;++j)
{
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");
}
}
既然无能更改,又何必枉自寻烦忧