[BZOJ3895]取石子(博弈+记搜)

来源:互联网 发布:用php写一个 编辑:程序博客网 时间:2024/06/18 00:16

题目描述

传送门

题解

我们可以通过石子的堆数和每一堆的个数计算出剩余的操作数,显然操作数为奇先手必胜,为偶先手必败。
若将=1的石子堆单独考虑,对于若干堆>1的石子,操作数为ni=1xi
那么我们可以记f[a][b]表示有a堆=1的石子,>1的石子操作数为b的状态(1表示先手必胜,0表示先手必败),然后进行记搜,对于a!=0的情况分类讨论,即可得出正确的解。

代码

#include<iostream>#include<cstring>#include<cstdio>using namespace std;int T,n,x;int f[100][51000];inline int dp(int a,int b){    //如果没有石子数为1的堆,则操作数为奇先手必胜,为偶先手必败     if (a==0) return b&1;    //若操作数为1此时b部分只有1个石子 归到a里面去     if (b==1) return dp(a+1,0);    if (~f[a][b]) return f[a][b];    int &re=f[a][b];    //将一个石子数为1的堆和一个不为1的堆合并     if (a&&b&&!dp(a-1,b+1)) return re=1;    //将两个石子数为1的堆合并    if (a>=2&&!dp(a-2,b+2+(b?1:0))) return re=1;    //从石子数为1的堆里拿走一个    if (a&&!dp(a-1,b)) return re=1;     //将两个石子不为1的堆合并或 从石子数不为1的堆里拿走一个    if (b&&!dp(a,b-1)) return re=1;    return re=0;}int main(){    scanf("%d",&T);    memset(f,-1,sizeof(f));    while (T--)    {        scanf("%d",&n);        int a=0,b=0;        for (int i=1;i<=n;++i)        {            scanf("%d",&x);            if (x==1) a++;            else b+=x+1;        }        b--;        puts(dp(a,b)?"YES":"NO");    }}

总结

1、博弈的题考虑记搜

0 0