zoukankan      html  css  js  c++  java
  • 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 }
  • 相关阅读:
    nginx获取上游真实IP(ngx_http_realip_module)
    配置NFS固定端口
    elasticsearch 占CPU过高
    jdk集合常用方法分析之HashSet和TreeSet
    SparkSQL使用之如何使用UDF
    SparkSQL使用之JDBC代码访问Thrift JDBC Server
    SparkSQL使用之Thrift JDBC server
    SparkSQL使用之Spark SQL CLI
    jdk分析之String
    jdk集合常用方法分析之ArrayList&LinkedList&以及两者的对比分析
  • 原文地址:https://www.cnblogs.com/zhuyutian/p/5792788.html
Copyright © 2011-2022 走看看