題意:有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;
}