题意是黑板上有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)即可表示每个状态。然后扫其后续状态即可。具体的状态转移,看代码吧。
代码如下:
#include"cstdio"
#include"iostream"
#include"cstring"
#include"algorithm"
#include"cstdlib"
#include"vector"
#include"set"
#include"map"
#include"cmath"
using namespace std;
typedef long long LL;
const LL MAXN=; int f[][];
int sg(int one,int m)
{
if(f[one][m]!=-) return f[one][m]; if(one==) return f[one][m]=((m%)==);
if(m==) return f[one][m]=sg(one+,); if(!sg(one-,m)) return f[one][m]=; // one中移出1个 if(m> && !sg(one,m-)) return f[one][m]=; // m操作1次 if(m> && !sg(one-,m+)) return f[one][m]=; // one中移动一个到m if(one>=) // 两个one合并
{
if(m> && !sg(one-,m+)) return f[one][m]=;
else if(m== && !sg(one-,m+)) return f[one][m]=;
} return f[one][m]=;
}
int main()
{
#ifdef LOCAL
freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
#endif
memset(f,-,sizeof(f));
int t;
scanf("%d",&t);
for(int tt=;tt<=t;tt++)
{
int n,one=,m=;
scanf("%d",&n);
for(int i=;i<=n;i++)
{
int tmp;
scanf("%d",&tmp);
if(tmp==) one++;
else m+=(tmp+);
}
if(m) m--;
printf("Case #%d: ",tt);
if(sg(one,m)) printf("Alice\n");
else printf("Bob\n");
}
return ;
}