题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1729
题意:2个玩家,有N个箱子,每个箱子的大小是Si,游戏开始前,就有一些石子在这些箱子里了。
游戏者轮流选择箱子,然后把石子放入箱子里。并且放入的石子数量不能大于原来箱子里就有的石子的数量的平方。
比如说,一个箱子里已经有了3个石头,则可以放1-9个石头(小于箱子的容量)。
当轮到某人时,不能再放石子则为输。
问能否找到一种策略使先手必赢。
学习了一下SG函数。
在公平的组合游戏中(游戏规则对于两个玩家不加区分),可以把所有可能出现的状态看作是图的节点。如果从一个状态可以通过一步转移到另一个状态,则在两点之间连一条有向边,这样就得到了一个状态图。
假设游戏不会出现平局,即状态图是有向无环图的话,所有状态可以分为两种,P态和N态。
P态:对于前一个玩家来说是必胜的。
N态:对于后一个玩家来说是必胜的。
一个状态被称为终止状态,如果当前状态下游戏不再继续进行。大部分游戏中,终止状态都是P态。就是前一个玩家走完后,游戏结束。
从定义可以知道,任意一个P态,它要么是终止状态,要么它可以转移到的状态(后继状态)都是N态,而对于任意一个N态,它至少有一个后继状态为P态。
SG函数:对于任意x,它的SG函数值g(x) = mex{g(y)|y是x的后继状态},其中mex是一个对于非负整数集合S的运算。mex(S)为S中没有出现的最小非负整数,对于一个终止状态,因为它没有后继状态。所以它的SG函数为0。
如果知道一个状态的SG函数值,则可以快速判断该状态是P态还是N态,对于一个状态们如果这个状态的SG值等于0,那么这个状态是P态(先手必败),否则就是N态。
如果有个多个组合,则sum=sg1 ^ sg2 ^ sg3 ^ ……sgn。
结论:一个游戏的初始局面是必败态当且仅当sum=0。
参考:http://blog.163.com/scuqifuguang@126/blog/static/171370086201101711276278/
思路:
参考:http://www.cnblogs.com/vongang/archive/2011/09/27/2193375.html
因为放入的石子数量不能大于原来箱子里就有的石子的数量的平方。
设每个箱子的容量为Si,已有的石子数量为Ci.
所以寻找一个p,使得p*p+p<S
1.Ci > p。那么一次放入石子数量肯定得到Si。
因为sg(S,S) = 0。
状态(S,S-1)的后继状态只有(S,S)。所以sg(S,S-1) = 1。同理sg(S,S-2) =2....
所以这种情况下sg(S,C) = S-C。先手必胜。
2. Ci = p。这种情况下。因为最多只能放p*p,且p*p+p<S。所以这个状态是P态。sg(S,C) = 0。先手必败。
3.Ci < P。这种情况下,到底是什么态就不确定了。
这里是递归求,再把p当成S,然后求sg。这里其实有一点不理解。
我是这样想的:
sg(p,c)。
如果求得x*x+x < p。
如果c = x。那么接下来的玩家就不能到(S,p)这个点。
所以下下个轮到的玩家可以达到(s,p)这个点。则下下下个玩家就不能到S。而下下下个玩家可以到S。
也就是说 先手必败。
而如果C>X。那么下一个玩家就可以达到(S,p)这个点。先手必胜。
如果C<X,继续递归。
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <string> 5 #include <algorithm> 6 #include <cmath> 7 using namespace std; 8 int s, c, N, ans; 9 int sg(int s, int c){ 10 int p = sqrt(double(s)); 11 while(p*p+p >= s) p--; 12 if(p < c) return s-c; 13 else if(p == c) return 0; 14 else return sg(p,c); 15 } 16 int main(){ 17 int tt = 0; 18 while(scanf("%d", &N) && N){ 19 int ans = 0; 20 while(N--){ 21 scanf("%d%d", &s, &c); 22 ans ^= sg(s, c); 23 } 24 tt++; 25 printf("Case %d: ", tt); 26 if(ans == 0) printf("No "); 27 else printf("Yes "); 28 } 29 30 return 0; 31 }