如果我写的有不对的地方,请大牛指正!Thanks!
什么是Nim?Nim是一种经典的博弈论模型,是组合游戏(Combinatorial Games)的一种,属于“Impartial Combinatorial Games”(以下简称ICG)。下面来一个经典的Nim游戏:
有N堆石子,每堆有a[n]个,两个玩家进行博弈,轮流从N堆石子中的一堆取任意个数的石子,可以全部取走。当轮到某人取的时候发现面前已经没有石子了(囧),那这个人就输了。。。问是否有先手必胜策略?
看完题之后有点摸不着头脑,仔细分析就会发现里面的玄机。
如果只剩一堆石子,那么先手必胜(废话,全拿走就赢了);
如果剩两堆石子,有两种情况:①两堆石子一样多,此时先手没有必胜策略,反而后手有必胜策略;
②两堆石子不一样多,可以取数量多的使两堆数量相等,则先手必胜。
看完第二种情况是不是晕了?把两堆石子取相等之后,如果对手在某一堆里拿若干颗,你就可以在另一堆中拿同样多的颗数,这样必胜,不信自己试试;换言之,如果你面对两堆数量相同的石子,那么另一名玩家就可以用上面所说的必胜策略,所以你就会输的。
游戏玩完了,下面是定义:
ICG问题的特征:
1.两个人参与,交替走棋;
2.可能的走法在一个有限的集合里选取;
3.游戏局面无后效性,未来与过去无关;
4.如果某选手无法走动,则判负;
定义Previous-position,为上一次move的人有必胜策略的局面,即先手必败或后手必胜。
定义Next-position,为本次move的人有必胜策略的局面,即先手必胜或后手必败。
然后我们来结合问题分析一下:我们假设当前局面为(3,3),显然(3,3)是一个P-position。(3,3)的子局面(也就是通过合法移动可以导致的局面)有(0,3)(1,3)(2,3);(0,3)的子局面有(0,0)(0,1)(0,2),显然(0,0)是一个P-position,(0,3)是一个N-position。只要找到一个是P-position的子局面就能说明是N-position。(1,3)的后继中(1,1)是P-position(因为(1,1)的唯一子局面(0,1)是N-position),所以(1,3)也是N-position。同样可以证明(2,3)是N-position。所以(3,3)的所有子局面都是N-position,它就是P-position。通过一点简单的数学归纳,可以严格的证明“有两堆石子时的局面是P-position当且仅当这两堆石子的数目相等”。
对于这类问题,有一个简单的算法,即求所有终止情况的异或和,如果异或和为0则为P-position,反之则为N-position。
证明……这个证明写得比较蛋疼,结合自己理解,跟网上的比较相似,毕竟我又不是神马数学系的。
需证明三个命题: 1、终止位置所有状态都为0为P-position;2、N-position局面一定可以移动到某个P-position;3、P-position的局面无法移动到某个P-position。
命题一使用上面提到的证明。
命题二:对于某个局面a[1..n],若a[1] xor a[2]xor ... xor a[n]=0,一定存在某个合法的移动,将a[i]改变成a[i]'后满足a[1] xor a[2]xor ...xor a[i]' xor...xor a[n]=0。不妨设a[1] xor a[2]xor...xor a[n]=k,则一定存在某个a[i],它的二进制表示在k的最高位上是1.这时a[i] xor k<a[i]一定成立。则我们可以将a[i]改变成a[i]'=a[i] xor k,此时a[1] xor a[2] xor ..xor a[i]xor...xor a[n]=a[1] xor a[2] xor...xor a[n] xor k=0。
命题三:异或运算满足消去率。消去率即在一个无零因子的环中,对任意的a,b,c∈R,c≠0,如果ac=bc或ca=ba,则a=b。所以对于某个局面a[1..n],若a[1] xor a[2]xor ... xor a[n]=0,经过某种合法移动后满足a[1] xor a[2]xor ...xor a[i]' xor...xor a[n]=0,则可证明a[i]=a[i]',所以这不是合法移动。
证明完毕。
下面就用例题说明。
Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 4928 | Accepted: 2762 |
Description
Input
Output
Sample Input
2 45 45 3 3 6 9
Sample Output
No Yes
中文翻译(软件翻译不了,还得人工- -):
这是一个简单的游戏。在这个游戏里,有很多堆火柴很两个玩家。两个玩家轮流进行游戏。在每一回合中,玩家可以从一堆中取走任意数量的火柴(当然也可以全部取
走,但取走的数量不能为0或者大于堆中的火柴数)。如果一个玩家的回合过后,没有火柴剩余了,那么那个玩家就是胜利者。假设两个玩家都很聪明,你的任务就是告诉
我先取的玩家会不会赢。
program POJ_2234; var i,a,n,ans:longint; begin while not eoln() do begin read(n); read(ans); for i:=1 to n-1 do begin read(a); ans:=ans xor a; end; if ans=0 then writeln('No')else writeln('Yes'); end; end.