https://www.acwing.com/problem/content/1323/
思路:
感觉一定要很聪明脑洞大开的那种才能想出来
记忆化搜索
先考虑一种相对简单的情况。
假设题目给出的所有堆的个数都大于1。
设 总操作数 b = 堆数 + 石子总数 - 1,想到 b 为 奇数的时候 先手必胜。
简单情况时,始终保证所有堆的个数大于1,若某堆的石子个数=1了,则将这堆和别的堆合并,给到对面选手的状态,还时保持了所有堆的个数大于1.
设石子个数为1的堆有a个
定义 f ( a , b )
① 后继状态,从a中拿一个,f(a-1,b),如果是sg = 0的话,代表当前是必胜态。
② 后继状态,从>1的堆中拿一个,f(a,b-1),操作数减1,如果sg=0的话,代表当前是必胜态
③ 后继状态,两个>1的堆合并,f(a,b-1),操作数减1,如果sg=0的话,代表当前是必胜态
④ 后继状态,两个=1的堆合并,f(a-2,b+(3 或 2) ,正常是3,如果b=0的话等于2,因为定义时是-1,如果b本身有值,就代表已经减过了不用减了。
④ 后继状态,一个=1的堆和>1的堆合并,f(a-1,b+1),b中某个堆多了1。若sg=0,则当前必胜。
处理边界:
如果a = 0,则就回到上面的简单情况 b为奇数先手必胜
如果b = 1,则代表当前只剩1堆了,并且这堆里只剩下1,所以返回dp(a+1,0),a中多了一堆,b中没有堆了。
#include<bits/stdc++.h>
using namespace std;
const int N = 55,M = 50050;
int f[N][M];
int t,n;
int dp(int a,int b){
int &v = f[a][b];
if(v != -1) return v;
if(a == 0) return v = b % 2;
if(b == 1) return v = dp(a+1,0);
if(a && !dp(a-1,b)) return v = 1;
if(b && !dp(a,b-1)) return v = 1;
if(a >= 2 && !dp(a-2,b + (b?3:2))) return v = 1;
if(a && b && !dp(a-1,b+1)) return v = 1;
return v = 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++){
int x;
scanf("%d",&x);
if(x == 1) a++;
else b += b? x+1:x;
}
if(dp(a,b)) puts("YES");
else puts("NO");
}
return 0;
}