博弈论(四)——#10246. 「一本通 6.7 练习 3」取石子

题目链接:https://loj.ac/problem/10246
解题思路
首先,我们将石堆分为热闹堆和寂寞堆,热闹堆的石子数大于1,寂寞堆的石子数等于1。我们设dp[i][j]表示有i个寂寞堆,和j次热闹堆操作下一次操作的人是否能取胜。
我们发现,如果只有热闹堆,只需要看当前能操作的数量的奇偶,就能判断之前操作的是否获胜,dp[0][j]=j&1。
如果此时有寂寞堆,则要分情况讨论。

  • 如果有大于等于两个寂寞堆,我们可以将寂寞堆合并,dp[i][j]=~dp[i-2][j+2]。如果此时还有热闹堆,还要多一次合并的操作,即dp[i][j]=~dp[i-2][j+2+(j?1:0)]。
  • 可以从寂寞堆里拿一个石头,dp[i][j]=~dp[i-1][j]。
  • 如果有热闹堆,可以从热闹堆里拿一个石头,dp[i][j]=~dp[i][j-1]。
  • 还可以将寂寞堆与热闹堆合并,dp[i][j]=~dp[i-1][j+1]。

除此以外,如果热闹堆操作数变为1了,那么此时热闹堆就变成了寂寞堆,dp[i+1][0]=dp[i][1]。
AC代码

#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=55,M=1005;
int dp[N][N*M],a[N];
int dfs(int num,int sum)
{
    if(num<=0&&sum<=0)
    return 0;
    if(dp[num][sum]!=-1)
    return dp[num][sum];
    if(num<=0)
    return dp[num][sum]=sum&1;
    if(sum==1)
    return dp[num][sum]=dfs(num+1,0);
    dp[num][sum]=0;
    if(num&&!dfs(num-1,sum))//拿一个寂寞堆的石子
    return dp[num][sum]=1;
    if(sum&&!dfs(num,sum-1))//把一个热闹堆里拿掉一个石子
    return dp[num][sum]=1;
    if(num&&sum&&!dfs(num-1,sum+1))//把一个寂寞堆合并到热闹堆上
    return dp[num][sum]=1;
    if(num>1&&!dfs(num-2,sum+2+(sum?1:0)))//把两个寂寞堆合并
    return dp[num][sum]=1;
    return dp[num][sum];
}
int T,n,res,cnt;//res能操作热闹堆的总数,cnt寂寞堆的数量
int main()
{
    scanf("%d",&T);
    memset(dp,-1,sizeof(dp));
    while(T--)
    {
        cnt=res=0;
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
        {
            scanf("%d",&a[i]);
            if(a[i]==1)
            cnt++;
            if(a[i]>1)
            res+=a[i]+1;
        }
        if(res)
        res--;
        int ans=dfs(cnt,res);
        if(ans)
        puts("YES");
        else
        puts("NO");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_44491423/article/details/108470421
今日推荐