题意是黑板上有n个数\(S_i\)。每次操作可以把其中一个数减1或者将两个数合并为一个数。一个数变为0时,则不能再对其操作。
思路是发现最大的可操作次数为\( \sum S_i\)+(n - 1)。\( \sum S_i\)是把所有数消除需要的操作数。(n-1)表示我们最多可以合并(n-1)次。
同时我们发现,总操作数的奇偶决定了胜负。换言之,合并的次数决定论胜负。
当1个数为1时,假如我们将其消去,则合并的次数减1,总操作数的奇偶改变。
那么我们首先考虑,所有的数都>=2的情况,假如这种情况对应的最大的可操作次数为先手胜。那么因为所有数>=2,所以无论后手怎么操作,最多把某个数变为1。当后手把某个数变为1,先手将这个1与其他数合并,则不改变最大的可操作次数。
换言之,当每个数都>=2时,其最后的操作总数必然等于最大的可操作次数。也就是说,假如没有数是1,那么胜负则已经决定了。
所以我们对每个状态,只需要用1的个数和非1的数的个数,即可表示。
那么我们用one表示1的个数,m表示非1的数的总可操作数。
对于所有非1的数字,其变为1的情况只有m=1一种。
所以我们用dp(one,m)即可表示每个状态。然后扫其后续状态即可。具体的状态转移,看代码吧。
代码如下:
1 #include"cstdio" 2 #include"iostream" 3 #include"cstring" 4 #include"algorithm" 5 #include"cstdlib" 6 #include"vector" 7 #include"set" 8 #include"map" 9 #include"cmath" 10 using namespace std; 11 typedef long long LL; 12 const LL MAXN=30; 13 14 int f[60][60000]; 15 int sg(int one,int m) 16 { 17 if(f[one][m]!=-1) return f[one][m]; 18 19 if(one==0) return f[one][m]=((m%2)==1); 20 if(m==1) return f[one][m]=sg(one+1,0); 21 22 if(!sg(one-1,m)) return f[one][m]=1; // one中移出1个 23 24 if(m>0 && !sg(one,m-1)) return f[one][m]=1; // m操作1次 25 26 if(m>0 && !sg(one-1,m+1)) return f[one][m]=1; // one中移动一个到m 27 28 if(one>=2) // 两个one合并 29 { 30 if(m>0 && !sg(one-2,m+3)) return f[one][m]=1; 31 else if(m==0 && !sg(one-2,m+2)) return f[one][m]=1; 32 } 33 34 return f[one][m]=0; 35 } 36 int main() 37 { 38 #ifdef LOCAL 39 freopen("in.txt","r",stdin); 40 // freopen("out.txt","w",stdout); 41 #endif 42 memset(f,-1,sizeof(f)); 43 int t; 44 scanf("%d",&t); 45 for(int tt=1;tt<=t;tt++) 46 { 47 int n,one=0,m=0; 48 scanf("%d",&n); 49 for(int i=1;i<=n;i++) 50 { 51 int tmp; 52 scanf("%d",&tmp); 53 if(tmp==1) one++; 54 else m+=(tmp+1); 55 } 56 if(m) m--; 57 printf("Case #%d: ",tt); 58 if(sg(one,m)) printf("Alice\n"); 59 else printf("Bob\n"); 60 } 61 return 0; 62 }
时间: 2024-10-05 12:51:01