HDU 5794 A Simple Nim 2016多校第六场1003
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5795
题意:给你n堆石子,每一堆有ai石头,每次有两种操作,第一种在任意一堆取出任意数量的石头但不能为0,第二种把一堆石头分成三堆任意数量的石头(不能为0),谁先拿完谁就赢,问是先手赢还是后手赢。
题解:这就是一个Nim游戏!那么Nim游戏一般跟SG函数有关,所以这道题打表找规律即可。
关于SG函数,在这里也说一下吧!毕竟第一次接触。
Nim游戏与SG函数:http://baike.baidu.com/link?url=7zmLIrfwIKbxt1JcrcKJ0yzTaCcng_CjGml8OMkhD7N1Jy-AgVGUrfBocLAlzLiGueHj0Dw4EN04ho5VSJb6n_
首先我们考虑异或(^)运算,显然对于两个不相等的数异或值一定不等于0,反之一定为0。那么我们考虑两堆石头的时候,设定数量分别是a,b。当a=b的时候,也就是异或值为0,对于后手的操作,只要模仿先手即可,为什么?也就是先手取多少,后手就取多少,先手把其中一堆取完,那么后手一定能刚好把另一堆取完,所以后手必胜。那么当a≠b时,此时异或值不等于0,只要先手第一次从多的一堆取出石头使得a=b时,此时后手面对的局面是a=b(异或值为0),从刚才分析可以知道,此时对于a=b,先手跟后手刚好交换了次序,也就是说此时先手必胜。那么我们可以看到,对于两堆石头,数量相等时开始,此时的(从a=b开始)后手可以模仿先手一直到刚好取完。我们可以看到,异或值相等和不相等时分别表示两种局面。我们把对于一个人必败的局面称为P局面(此时异或值为0)。也就是说当先手或者后手面对的局面异或值为0的时候,他必败,另一个人必胜(都用最优策略)。
那么对于多堆石头,这个结论是否成立呢?答案是肯定的了。那么我们先从异或的运算方法来考虑。异或也就是按位异或,当二进制位中,同一位不同数值时该位异或值为1,相同数值为0。那么当数两个数a,b他们二进制最高位为第k位的时候,两个数的异或值范围为(0~2k+1-1)。由此,当n个数的异或值为c(c的二进制最高位为k)时,那么我们可以知道一定存数的最高位>=k,并且一定存在数>=c。那么从这里可以看出,当n堆石头异或值为0的时候,一次操作后使得异或值改变为c的时候,根据前面可以知道一定有数>-=c,也就是说下一次操作一定可以是的局面再变为0。那么我们可以总结出一个普遍化规律:当当前局面为0的时候,也就是P局面,无论此时的先手怎么操作,此时的后手一定可以使得局面再变为0。因此,n堆石头也符合规律。
那么,SG函数就是用来描述当前局面。准确来说,SG函数是描述有向无环图游戏局面的。首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。那么对于SG(x),sg(x)=mex{ sg(y) | y是x的后继 },y也就是x能够到达的子局面,比如说当前x=3,那么他可以取1个石头到达局面2,取两个到达局面1,取3个到达局面0,也就是说sg(3)=mex{sg(2),sg(1),sg(0)}。那么整个游戏的总和为sg=sg(0)^sg(1)^sg(2)^……^sg(n)。sg(0)=0。
对于我们来说,SG函数与“游戏的和”的概念不是让我们去组合、制造稀奇古怪的游戏,而是把遇到的看上去有些复杂的游戏试图分成若干个子游 戏,对于每个比原游戏简化很多的子游戏找出它的SG函数,然后全部异或起来就得到了原游戏的SG函数,就可以解决原游戏了。
回到本题中,题目两种操作,那么对于某一堆的sg值怎么求呢。我们考虑一堆石头数量为x时,那么对于第一种操作,子局面的sg是显然的。对于第二种操作,x分成a,b,c。a+b+c=x,那么这个操作产生的三堆,三个子局面,那么这个子游戏的总和就是sg(a)^sg(b)^sg(c)。比如x=7时,通过第二个操作可以产生子游戏1,2,4。那么这个子游戏的局面就是sg(1)^sg(2)^sg(4)。
根据以上,通过递推可知道:
sg[0]=0
当x=8k+7时sg[x]=8k+8,
当x=8k+8时sg[x]=8k+7,
其余时候sg[x]=x,(k>=0)。
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 int n; 5 int cnt; 6 7 int main() 8 { 9 int t; 10 cin>>t; 11 while(t--) 12 { 13 cnt=0; 14 cin>>n; 15 int a; 16 for(int i=0;i<n;i++) 17 { 18 scanf("%d",&a); 19 if(a%8==0) 20 cnt^=a-1; 21 else if((a+1)%8==0) 22 cnt^=a+1; 23 else 24 cnt^=a; 25 } 26 if(!cnt) 27 puts("Second player wins."); 28 else 29 puts("First player wins."); 30 } 31 return 0; 32 }