题目链接:https://vjudge.net/problem/UVA-1378
题意:
两个人玩游戏,有n堆石子,两人轮流操作:于第i堆石子中取走一块石子,然后再往第j、k堆中各添加一块石子。其中 i<j, j<=k。最后一次操作的为赢家,问先手能否必胜,如果能,请输出第一步操作。
题解:
1.把每个石子都看成是一个子游戏,所以在第i堆的其中一个石子,它的下一步为第j堆和第k堆;即这个子游戏又可以分解为两个子游戏。
2.由于在同一堆里的石子状态完全相同,所以当这堆石子的个数为偶数时,他们的SG值异或和为0;当个数为奇数时,他们的SG值异或和即为单独一个石子的SG值。
3.那么怎么求在第i堆里一个石子的SG值呢?一般情况下子游戏的下标是较小的,而处于第i堆的石子游戏又可以分解为处于第j、k石子的子游戏,然后i是小于j、k的,所以我们就把下标翻过来,这样就满足的SG的求值过程。初始状态为SG[0] = 0,即位于第0堆时,无法操作,即无后续状态,所以 SG[0] = mex{} = 0。
4.至于输出第一步,枚举ijk,得到一个必败状态即可。为何是必败状态?因为先手是处于必胜状态,当先手操作了一轮之后,轮到后手,而后手就处于必败状态。
代码如下:

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <vector> 6 #include <cmath> 7 #include <queue> 8 #include <stack> 9 #include <map> 10 #include <string> 11 #include <set> 12 using namespace std; 13 typedef long long LL; 14 const int INF = 2e9; 15 const LL LNF = 9e18; 16 const int MOD = 1e9+7; 17 const int MAXN = 30+10; 18 19 int a[MAXN], SG[MAXN], vis[1000]; 20 21 void getSG() 22 { 23 SG[0] = 0; 24 for(int i = 1; i<MAXN; i++) 25 { 26 memset(vis, 0, sizeof(vis)); 27 for(int j = 0; j<i; j++) 28 for(int k = 0; k<=j; k++) 29 vis[SG[j]^SG[k]] = 1; //下一个状态,由两个子游戏组成,故异或。 30 31 for(int j = 0;;j++) if(!vis[j]) { 32 SG[i] = j; 33 break; 34 } 35 } 36 } 37 38 int main() 39 { 40 getSG(); 41 int n, kase = 0; 42 loop: 43 while(scanf("%d",&n) && n) 44 { 45 int val = 0; 46 for(int i = 0; i<n; i++) 47 { 48 scanf("%d", &a[i]); 49 if(a[i]%2) val ^= SG[n-1-i]; //当个数为奇数时,才有效 50 } 51 52 printf("Game %d: ", ++kase); 53 for(int i = 0; i<n-1; i++) //枚举、模拟第一步操作 54 { 55 if(a[i]==0) continue; //如果这堆没有石子,那就不能取了。 56 for(int j = i+1; j<n; j++) 57 for(int k = j; k<n; k++) //取走i堆一块石子,往j、k堆中各加一个石子,即撤销i堆中一个子游戏 58 if((val^SG[n-i-1]^SG[n-1-j]^SG[n-1-k])==0){ //j、k堆中增加一个游戏。异或的作用是:有则去之无则添之。 59 printf("%d %d %d ", i, j, k); 60 goto loop; 61 } 62 } 63 printf("-1 -1 -1 "); 64 } 65 return 0; 66 }