题目位置: 2006年北京赛区的题目
http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4124
题意:给定n个石堆,每个石堆的石子个数不超过1000,A与B进行一向游戏,每次一个人选取3堆(i,j,k, i <j <=k),然后取走i堆的一个石子,给j,k各加入一个石子,(j,k)相同的话就两个,最后不能取的会输。问先取者是否有必胜策略,有的话输出第一步选取的3堆,否则输出-1 -1 -1。
思路:sg函数的变形,关键在于如何拆分游戏。
我们知道每次选取i,j,k,i取走一个,j,k各增加一个,这样的话就改变了i,j,k的奇偶性,所以对于所有的石子堆,只有当他为奇数时才有效果,偶数的话不影响结局。(因为偶数的话,第一个可以取i,j,k,那么第二个也可以取i,j,k,奇偶性不变,结果与第一个取之前的局面是一样的)
这样一来我们只要考虑奇数堆。。不妨设其棋子数为1个
接下来我们就讨论一下如何拆分。。
对于i上单个棋子,那么他可以给他后面任意选取的两堆各增加一个棋子,而增加的棋子也有同样的方法去给其后面的增加棋子。
这样一来,只要把i生出来的棋子全部归到第n堆,这便是一个对于i的单个棋子的游戏。。
所以很明显的,每个棋子都是一个单个的游戏,这样就拆分了游戏。。
接下来就是如何用sg函数表示了,
对于第i堆的每个石子,我们可以n-i-1个石子来表示他(因为他后面有这么多个,跟nim联系起来),用sg[n-i-1]来表示她的sg值
值得注意的是1):假设i可以拆分成j,k,那么其子状态sg[i] ^ sg[k]来计算,因为拆分出来的是2个独立博弈的和,
ps.我弱弱的博弈论。。。。
code:
/* Problem:UVA3688 Author:yzcstc Time:2013.5.24 */ #include <iostream> #include <cstring> #include <cstdio> #include <cstdlib> using namespace std; int sg[100], stone[30], tot, n; int find_sg(int v){ if (sg[v] != -1) return sg[v]; int vis[100]= {0}; for (int i = 0; i < v; ++i) for (int j = i; j < v; ++j) vis[find_sg(i)^find_sg(j)] = 1; int res = 0; while (vis[res]) res++; return sg[v] = res; } void init(){ memset(sg, -1, sizeof(sg)); sg[0] = 0; for (int i = 1; i < 23; ++i){ find_sg(i); // printf("%d\n", sg[i]); } } void solve(){ ++tot; int now = 0; for (int i = 0; i < n; ++i){ scanf("%d", &stone[i]); if (stone[i]&1) now ^= sg[n - i - 1]; } for (int i = 0; i < n; ++i) if (stone[i]) for (int j = i + 1; j < n; ++j) for (int k = j; k < n; ++k) if ((now ^ sg[n - k - 1] ^ sg[n - j - 1] ^ sg[n - i - 1]) == 0){ printf("Game %d: %d %d %d\n", tot, i, j, k); return ; } printf("Game %d: -1 -1 -1\n", tot); } int main(){ freopen("uva3688.in", "r", stdin); freopen("uva3688.out","w",stdout); init(); while (scanf("%d", &n)!= EOF && n) solve(); //fclose(stdin); fclose(stdout); }