首先给出基本定义:
对于一个游戏可能发生的局面x,我们如下定义它的sg值:
(1)若当前局面x为终结局面,则sg值为0。
(2)若当前局面x非终结局面,其sg值为:sg(x) = mex{sg(y) | y是x的后继局面}。
mex{a[i]}表示a中未出现的最小非负整数。举个例子来说:
mex{0, 1, 2} = 3, mex{1, 2}=0, mex{0,1,3}=2
sg定理:
对于多个单一游戏,X=x[1..n],每一次我们只能改变其中一个单一游戏的局面。则其总局面的sg值等于这些单一游戏的sg值异或和。
即:
sg(X) = sg(x[1]) xor sg(x[2]) xor … xor sg(x[n])
要证明这一点我们只要证明:
(1) 假设sg(x[1]) xor sg(x[2]) xor … xor sg(x[n]) = A,对于任意一个0 <= B < A,总存在一个X的后续局面Y,使得sg(Y) = B。
(2) 假设sg(x[1]) xor sg(x[2]) xor … xor sg(x[n]) = A,不存在一个X的后续局面Y,使得sg(Y) = A。
这是hiho里给出的解释,首先给出了定理,然后解释了定理并证明了,然后结合问题分析了问题应该怎么做。标准的中国式例题讲解思维。确实这样的逻辑会比较符合大多数人的思维。但是这里面是否又有些值得深思的呢?
代码就是依据定理来写的,求出所有的sg[].
另外,对于这个题每个sg值就有两种可能:
(1)不分堆:石子数量为k’=0..k-1,则sg(k’)
(2)分堆:石子变为2堆,数量为(1,k-1),(2,k-2),…,(k-1,1)。设第一堆的石子数量为i,则sg值为sg(i) xor sg(k-i)。(这里用到了sg定理)
那么可以推算出sg(k) = mex{sg(0), sg(i), sg(i) xor sg(k - i) | i = 1..k-1}。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <algorithm>
#define read freopen("q.in","r",stdin)
#define LL __int64
#define maxn 20004
#define inf 10000000
#define mod 142857
using namespace std;
int sg[maxn],a[maxn];
bool vis[maxn];
void Accepted()
{
int i,j;
sg[0]=0;
for(i=1;i<=maxn;i++)
{
memset(vis,0,sizeof(vis));
for(j=0;j<i;j++)
{
int t=sg[j]^sg[i-j];
vis[sg[j]]=1;
vis[t]=1;
}
for(j=0;j<maxn;j++)if(!vis[j])
{
sg[i]=j;
break;
}
}
}
int main()
{
int n,i,j,x;
Accepted();
scanf("%d",&n);
int res;
scanf("%d",&x);
res=sg[x];
for(i=1;i<n;i++)
{
scanf("%d",&x);
res^=sg[x];
}
if(!res)cout<<"Bob"<<endl;
else cout<<"Alice"<<endl;
}