题目链接: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;
}