题意:有n堆石子,编号为0~n-1。第i堆有Si个石子 ,两个人轮流操作。每次可以选3堆i,j,k(i<j<=k),且第i堆必须又石子,从第i堆中取出一个石子,往第j和第k堆各加入一个石子(若j==k ,加两个石子)。 不能操作的人输。分析先手是必败还是必胜 。若必胜要求给出字典序最小的必胜操作(i,j,k)。
分析:
先考虑这样一个简单点的游戏:[ 记为game(i) ]
同样有n个堆,但只有其中一个堆(设为i)有一个石子,操作规则和上面的游戏相同。问先手必胜还是必败?
如果你熟悉SG定理,一定可以马上想到解决方法。
SG[ i ] = mex{ SG[ j ] ^ SG[ k ] } , i > j >= k
再回到原来的问题,其实每个石子不就可以看做一个上面的简单游戏吗 , 这样再用一次SG定理就可以得到整个游戏的SG值了! ,至于字典序最小的操作 , 从小到大一次枚举即可 ,因为n<=23 ,毫无压力。
参考代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
int sg[50];
int mex(vector<int>&S){
sort(S.begin(),S.end());
if(S[0] > 0) return 0;
int s_size = S.size();
for(int i=1;i<s_size;i++) {
if(S[i]-S[i-1] > 1) return S[i-1]+1;
}
return S[s_size-1] + 1;
}
int SG(int x){
if(sg[x] != -1) return sg[x];
vector<int>S;
for(int i=0;i<x;i++)
for(int j=0;j<=i;j++)
S.push_back(SG(i)^SG(j));
return sg[x] = mex(S);
}
int s[50],n;
int sum_sg(){
int g = 0;
for(int i=1;i<n;i++){
if(s[i]&1) g^=SG(i);
}
return g;
}
int main()
{
memset(sg,-1,sizeof(sg));
sg[0] = 0 ,sg[1] = 1;
int cas = 1;
while(scanf("%d",&n)!=EOF && n){
for(int i=n-1;i>=0;i--){ //堆的序号翻转过来了
scanf("%d",&s[i]);
}
bool ok = false;
int i,j,k;
for(i=n-1;i>=1;i--) if(s[i]>0){
for(j=i-1;j>=0;j--){
for(k=j;k>=0;k--){
s[i]-- , s[j]++ , s[k]++;
if(sum_sg()==0) {ok=true; goto output ;}
s[i]++ , s[j]-- , s[k]--;
}
}
}
output :;
printf("Game %d: ",cas++);
if(ok) printf("%d %d %d\n",n-i-1,n-j-1,n-k-1);
else printf("-1 -1 -1\n");
}
return 0;
}