【JZOJ3819】【NOI2015模拟9.9】【hdu 4111】取石子(博弈+贪心+记忆化搜索)

Problem

这里写图片描述

Input

这里写图片描述

Output

这里写图片描述

Hint

这里写图片描述

Solution

  首先感喟一下,今天又是让我们看着一坨图片消耗脑细胞,所以OJ多没诚意,抄一下都不肯,也不知道用微信把图片转成文字。
  终结了我的胡思乱想,我发现这题是我几乎从未接触过的博弈,感觉可能切不了。此时右边的ZJQ说这题不需要用什么鬼sg函数,只是一道普通的博弈,想个几十分钟,或者不用,想个十几分钟就能想出来。我信了,于是在那里猛想,但是这道题的防御依旧固若金汤。
  你可以想到,最终我还是没想出来,并且连暴力都没想出来,比赛时索性没交这题。
  下面是解题思路。
  首先,设f[a][b]表示a为大小为1的堆的数目,b为其余堆的合并后再取完的步数(即其余堆的大小和+数目-1)时先手是否必胜。至于为什么要这么设状态,因为这样可以把许多冗余状态合并在一起,优化时间复杂度;而至于为什么这样设状态是对的,就是说,有无可能有两种局面恰好均满足上述状态,但一个是必胜,一个是必败,我只能说,我也不知道该怎么证明,我们可以分类讨论一下所有堆大小均大于1的和有若干堆大小为1的情况。
  对于前者,我们可以再分类讨论一下b的奇偶性。若b为奇数,则先手必胜;否则必败。这个证明很简单,因为如果b为奇数,则先手可以一直在合并,这样的话会让b变为偶数;而后手有两种选择:1.吃掉一个;2.跟着先手合并。不管他怎么选,都会将b从偶数变成一个奇数,因为他选1的话,由于所有堆的大小均大于1,吃了一个不能减少堆数,我们则可以趁机将那个被他吃掉一个的堆合并掉,这样就可以保证从整个局面看,轮到后手时,始终不会出现大小为1的堆,也就不会给后手翻盘的机会。所以b为奇数,先手必胜。而如果b为偶数,先手不管怎么搞都会把它变成奇数,则后手进入必胜局面。
  对于后者,则没有那么简单了。
  首先,我们考虑f[a][b]可以从那几个状态推过来。
  显然由下面四个状态转移而来:
1. f[a-2][b+2+(b?1:0)](把两个为1的堆合并,所以a减2,b由于大小增加加了个2,而如果原本b中无堆,则不必合并;否则我们须花费1步,把新建的这个大小为2的堆合并到其他地方)
2. f[a-1][b](取走某个大小为1的堆)
3. f[a-1][b+1](将某个大小为1的堆与某个大小大于1的堆合并)
4. f[a][b-1](将某个大小大于1的堆吃掉一个石子或者合并两个这样的堆)
  需要满足的条件显然。
  至于边界条件,显然为f[0][b]=b%2。
  注意,如果我们不断地做4操作,可能会将b退化为大小为1的堆,所以当b==1时,b中肯定只剩下了一个堆,且此堆的大小肯定为1,于是我们把这个局面变为f[a+1][0]。
  对了,还有一点很重要,就是我们在每次做完记忆化搜索的时候(或开始做之前),我们不必讲记忆化的内容清空,因为它的读入并不影响每个局面胜败与否。这样的话,我们就可以藐视多组数据了。
  时间复杂度: O(T+N2ai) O ( T + N 2 a i )

Code

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define N 51
#define A 1001
#define fo(i,a,b) for(i=a;i<=b;i++)
int T,t,i,n,a,b,x,f[N][N*A];
int dfs(int a,int b)
{
    if(f[a][b]>=0)return f[a][b];
    if(!a)return f[a][b]=b%2;
    if(b==1)return f[a][b]=dfs(a+1,0);
    int t=1;
    if(a>1)t=min(t,dfs(a-2,b+2+(b?1:0)));
    if(a)t=min(t,dfs(a-1,b));
    if(a&&b)t=min(t,dfs(a-1,b+1));
    if(b)t=min(t,dfs(a,b-1));
    return f[a][b]=!t;
}
int main()
{
    scanf("%d",&T);
    memset(f,-1,sizeof f);
    fo(t,1,T)
    {
        scanf("%d",&n);
        a=b=0;
        fo(i,1,n)
        {
            scanf("%d",&x);
            if(x==1)
                    a++;
            else    b+=x+1;
        }
        b-=(bool)b;
        printf(dfs(a,b)?"YES\n":"NO\n");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值