【bzoj3895】【取石子】【博弈论+记忆化搜索】

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

Sample Output

YES
NO
NO

HINT

100%的数据满足T<=100,  N<=50. ai<=1000
题解:考虑如果不存在石子数为1的堆。
          设这种状态下操作数为x,显然x等于石子总数加操作数减1。
          可以证明当x为奇数时先手必胜。当x为偶数时先手必败。
          如果只有1堆石子,该结论显然成立。
          如果有多堆石子,每堆石子个数都大于1,并且x为偶数,下面我们证明这样先手必败。
          1.如果先手选择合并两堆石子,那么每堆石子的个数依然大于1,x变为奇数。
          2.如果先手选择从一堆石子数大于2的堆中拿走一枚石子,那么同上每堆石子个数依然大于1,x变为奇数。 
          3.如果先手选择从一堆石子数等于2的堆中拿走一枚石子,那么后手可以合并剩下的1枚石子到任意一个堆。
          那样x的奇偶性不变,每堆石子的个数依然大于1.
          综上所述,结论成立。
          然后考虑如果存在石子数为1的堆.我们设石子数为1的堆的个数为x,其余堆的个数为y.
          令f[x][y]为这种状态下先手必胜或是必败。
          每次转移枚举所有可能的操作。记忆化搜索即可。
代码:
        
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,f[55][50055],T,x,a,b,vis[55][50055];
int dfs(int a,int b){
  if (a==0) return b&1;
  if (b==1) return dfs(a+1,0);
  if (vis[a][b]) return f[a][b];vis[a][b]=1;
  if (a&&!dfs(a-1,b)) return f[a][b]=1;
  if (a&&b&&!dfs(a-1,b+1)) return f[a][b]=1;
  if (a>=2&&!dfs(a-2,b+2+(b?1:0))) return f[a][b]=1;
  if (b&&!dfs(a,b-1)) return f[a][b]=1;
  return f[a][b]=0;
}
int main(){
  scanf("%d",&T);
  while (T--){
    scanf("%d",&n);a=0;b=-1;
    for (int i=1;i<=n;i++){
       scanf("%d",&x);
       if (x==1) a++;
       else b+=x+1;
    }
   if (b==-1) b=0;
   if (dfs(a,b)==1) printf("YES\n");
   else printf("NO\n");
  }	
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值