题意: 有n个瓶子,之后你每次可以选三个(或两个)瓶子i,j,k(i< j <= k ),将第i个瓶子里取出一颗石子,在第j,k个瓶子里投入一颗石子,最后没有办法操作者输,他现在问你第一步有几种取法,并输出第一步中字典序最小的一个取法。
思路:我们对于任意一堆中的任意一颗石子,那么他的子游戏是往j和k两个瓶子中扔进一个石子,那么对于一堆石子呢就是一颗石子一颗石子的异或和,故,如果一堆石子是偶数个,那么我们就可以不管他,因为他们异或和最后是0,那么奇数堆就是他本身,所以我们现在就可以把游戏抽象成在第i个位置有1个石子,他的后继游戏是可以往j和k个位置放入一颗石子,那么当前位置i的sg值其实就是 sg(j) ^ sg(k) 有了这个表我们就可以得到胜负的关键了,那么我们怎么看先手胜还是先手败呢?我们先把所有的情况异或一遍,之后进行取消操作(也就是 ans = i ^ j ^ k , ans ^ i 这样就相当于我们只异或了j和k)如果他是0的话也就是说我先手可以通过这一步将他变成先手必胜态,所以答案++ 就好了
代码:
#include <stdio.h> #include <string.h> #include <algorithm> #include <iostream> using namespace std; const int maxn = 111 + 10; int a[maxn],SG[maxn]; int n; int getsg(int x) { if(x == n )return 0; if(SG[x] != -1) { return SG[x]; } int vis[maxn]; memset(vis,0,sizeof(vis)); for(int i = x + 1 ; i <= n ; i++)//注意j和k可能会相等 { for(int j = i ; j <= n ; j++) { vis[getsg(i) ^ getsg(j)] = 1; } } for(int i = 0 ; ; i ++) { if(!vis[i]) { return SG[x] = i; } } } int main() { int t; scanf("%d",&t); while(t--) { memset(a,0,sizeof(a)); memset(SG,-1,sizeof(SG)); scanf("%d",&n); int ans = 0 ; for(int i = 1 ; i <= n ; i++){ scanf("%d",&a[i]); if(a[i] & 1) ans = ans ^ getsg(i); } int cur = 0 ; for(int i = 1 ; i <= n ; i++) { for(int j = i+1; j <= n ; j++) { for(int k = j ; k <= n ; k ++) { if(!(ans ^ getsg(i) ^ getsg(j) ^ getsg(k)))//取消i的操作,因为i的sg值其实是他的所有后继游戏的异或和所以这里要异或 j和k { if(cur == 0) { printf("%d %d %d\n",i-1,j-1,k-1); } cur++; } } } } if(cur == 0) puts("-1 -1 -1"); printf("%d\n",cur); } }