【题解】#10246. 「一本通 6.7 练习 3」取石子
Description
Sample Input
3
3
1 1 2
2
3 4
3
2 3 5
Sample Output
YES
NO
NO
Solution
我天,真神仙题!
这题其实是博弈论DP,一开始还想着直接用SG * 过去。
我们先从最简单的入手:
只有一堆石子时我们可以不考虑合并造成的影响那么一个人赢的情况只有可能是他剩下可以进行的操作数是奇数。
(这里我们发现剩下可以进行的操作数只有取一个石子)
那如果有两个堆。
(假设只有一个石子的堆叫做寂寞堆,大于一个石子的堆叫做热闹堆)
那么我们要分两种情况分别讨论:
\(1.\) 我们有两个堆,一个寂寞堆一个热闹堆。
那么我们假设寂寞堆 \(1\) 个石子,热闹堆 \(2\) 个石子,那么很明显我们当前只有要么从两个堆里取一个,要么合并。
-
首先考虑合并:合并之后热闹堆的奇偶性变了,同时合并之后取的是对手,这样就保证了对手赢。
-
考虑先把寂寞堆取完,那么我们在热闹堆中是可以直接根据奇偶求出谁会赢。如果先取热闹堆对手是有赢的策略的。
到这里我们发现好像寂寞堆会影响答案,如果只有热闹堆,热闹堆之间的合并不会改变他们的奇偶,对我们考虑没有影响,只会导致赢输的人不一样。
但如果出现了寂寞堆,寂寞堆的合并会影响热闹堆的奇偶性,所以要特殊考虑寂寞堆。
这个时候就要我们上博弈论DP了 然而我不知道为什么要上(逃
设状态 \(f[i][j]\) 表示有 \(i\) 个寂寞堆,\(j\) 次对于热闹堆的操作时当前操作的人是赢还是输。
这个状态好诡异
我们转移怎么办呢?
分类讨论一下:
\(1.\) 寂寞堆操作
- 寂寞堆取一个石子,很明显转移到 \(f[i-1][j]\)
- 寂寞堆合并(2个寂寞堆) 转移到 \(f[i-2][j+2]\)(合并之后多了一个热闹堆,要对热闹堆进行两次取石子操作),但突然发现如果还有热闹堆的话我们还会多一次合并操作,那就转移到 \(f[i-2][j+2+(j?1:0)]\)
- 寂寞堆合并到热闹堆上,转移到 \(f[i-1][j+1]\)
\(2.\) 热闹堆操作
- 从热闹堆里取一个石子,需要考虑是否取了之后变为寂寞堆。也就是 \(j\) 是否为 \(1\)。两个转移 \(f[i+1][0](j==1),f[i+1][j-1]\)
这样之后好像就没啥子了。注意一下细节就莫得了。
#include<bits/stdc++.h>
using namespace std;
int T,n;
const int N=55,M=1005;
int a[N],f[N][M*N];
inline int dfs(int num,int sum){
if(num<=0 && sum<=0) return 0;
if(f[num][sum]!=-1) return f[num][sum];
if(num<=0) return f[num][sum]=(sum&1);
if(sum==1) return f[num][sum]=dfs(num+1,0);
f[num][sum]=0;
if(num && !dfs(num-1,sum)) return f[num][sum]=1; // 拿一个寂寞堆的石子
if(sum && !dfs(num,sum-1)) return f[num][sum]=1; // 把一个热闹堆里拿掉一个石子
if(num && sum && !dfs(num-1,sum+1)) return f[num][sum]=1; // 把一个寂寞堆合并到热闹堆上
if(num>1 && !dfs(num-2,sum+2+(sum?1:0))) return f[num][sum]=1; // 把两个寂寞堆合并
return f[num][sum];
}
int main(){
scanf("%d",&T);
memset(f,-1,sizeof(f));
while(T--){
int cnt=0,step=0;
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
if(a[i]==1) cnt++;
if(a[i]>1) step+=a[i]+1;
}
if(step) step--;
printf("%s\n",dfs(cnt,step)?"YES":"NO");
}
return 0;
}