题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=2176
http://acm.hdu.edu.cn/showproblem.php?pid=1850
http://acm.hdu.edu.cn/showproblem.php?pid=1851
http://acm.hdu.edu.cn/showproblem.php?pid=1907
*如对必胜态,必败态不是很理解的请移步上篇博客http://blog.csdn.net/sm9sun/article/details/53229146
题目描述:先抛一个取石子问题:
有n堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)
假设:
如果n为1时,那么对于我方来说是必胜态,我可以把全部石子拿走。
如果n为2时,那么我需要做的是逼迫对方拿光其中一堆,当然对方也不傻,所以我需要把石子变成1:1这样对方就必须拿光一堆。
所以,当我面对两堆不一样数量的石子时,我的操作是使其两堆石子数相等,之后无论对方怎么拿,我都再另一堆也拿相同的石子数,逐步逼近1:1
可见,这种情况我方为必胜态,反之,如果有两堆相同的石子,我方为必败态。
如果n>2呢?比如说3,那么先举几个例子看一下,如果含有0,比如说(0,2,2)那么就回到了(2,2)的状态。
如果是(1,2,3)不难发现,我方处于必败态,因为我无法一步将其变为(0,n,n),而对方在我做任何操作
(0,2,3)(1,1,3)(1,2,2)(1,0,3)(1,2,1)(1,2,0)后,都可以变成(0,n,n)
在这样的博弈中,我们不会轻易的拿掉一个独立的1,因为独立的1是完全颠覆胜败的操作
所以无论m有多少,我们都可以将其视为最终的m个0和1,这个1的奇偶性决定着此次博弈的胜败
所以(1,2,3 )我们可以将(2,3)当作是(0,1),我们不妨认为胜者必然会通过一轮双方都取2的操作来使其局势变为(1,0,1)
联系到0和1,我们就不难想到计算机中的二进制了,所以我们用到的就是计算机中的按位异或运算。
当我们面对一个(a,b,c)时,我们想办法使c变成a^b即可达到必胜态,因为a^b^(a^b)=(a^a)^(b^b) =0^0
m堆石子同理
举个例子:题中的(3,6,9)即0011,0110,1001。
0011^0110^1001的结果是1100,所以我们要看以上3个数字能否拿掉若干石子达到改变 1100这个值
0011 ^ 1100=1111 即3-15 不满足
0110 ^ 1100=1010 即6-10 不满足
1001 ^ 1100=0101 即9-5 可行
也就是说,我把9拿掉4后变成5,局势变为(3,6,5)0011,0110,0101
0011^0110^0101其结果为0000 满足对方的必败态。
#include<stdio.h>int main (){int a[200001],i,x,m;while(~scanf("%d",&m)&&m){ x=0;for(i=1;i<=m;i++){scanf("%d",&a[i]);x^=a[i];}if(!x)printf("No\n");else{printf("Yes\n");for(i=1;i<=m;i++){if((x^a[i])<a[i])printf("%d %d\n",a[i],x^a[i]);}}}return 0;}
题目1850、1851、1907同上
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1849
题目描述:一个1*n的棋盘,编号为0~n-1,有m个棋子(可重叠)在棋盘上,每次可以将一枚棋子左移直至编号0的位置
最后一位走棋者胜。
讲道理题目中棋子左移操作很仁义了(好评),因为这很容易能让我们联系到 当棋子到第0位置时即无法再移动。
所以这道题我们可以转化为: 有m堆石子(棋子),每堆石子有不同的个数(棋盘编号),你可以拿走若干个石子(左移),最后一位走棋者(直至一堆)为胜。
所以这也是完完全全没有任何加料的Nim博弈~
#include<stdio.h>int main(){int i,b,m;int nim;while(~scanf("%d",&m)&&m){nim=0;for(i=0;i<m;i++){scanf("%d",&b);nim^=b;}if(nim)printf("Rabbit Win!\n");else printf("Grass Win!\n");}return 0;}