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

题目描述

传送门

题解

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

代码

#include
#include
#include
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、博弈的题考虑记搜

你可能感兴趣的:(题解,dp,博弈)