• 尼姆博弈


    以下内容许多都是看这里的:http://blog.csdn.net/acm_cxlove/article/details/7854530

    尼姆博弈(Nim Game):两个人要玩一个游戏,有n堆纸牌,每堆有若干张,每人轮流取物纸牌,每次可以从其中一堆中取出若干张(不能不取),谁最后取完所有纸牌谁就获胜。

    尼姆博弈有一个关键的定理:nim-sum是所有堆的纸牌数的异或值,比如有3堆纸牌,数量分别为a1,a2,a3。如果a1^a2^a3=0(这里的^是异或的意思),则先手必败,反之先手可以获胜。

    结论一(取完获胜):

    1.可以通过计算所有堆的nim-sum,nim-sum!=0先手胜,反之后手胜。

    2.可以用计算后的nim-sum分别与所有堆的元素进行异或操作,如果得到结果小于原来堆的元素,则为可选操作。

    我们这里有两个问题:

         一、有n堆纸牌,每堆有若干张,轮流取,每次可以从某一堆取若干张,谁取完就获胜。经典的尼姆博弈。

          二、有n堆纸牌,每堆有若干张,轮流取,每次可以从某一堆取若干张,谁取完就输掉。变形,把胜负条件反转一下。

    我们接下来分析一下,设数量为1的堆为孤单堆,数量大于1的为充裕堆。令sum=a1^a2^...an。

         设置状态①S0态,sum!=0&&充裕堆=0(奇数个为孤单堆)。

         ②T0态,sum=0&&充裕堆=0(偶数个孤单堆)。

         ③S1态:充裕堆=1,必胜态(无论问题一还是二都是必胜,可以通过对富裕堆取得只剩一张牌或者全部取完,改变剩余n-1个孤单堆的奇偶性)。

         ④S2态,sum!=0&&充裕堆>=2。

         ⑤T2态,sum=0&&充裕堆>=2。

    以上5种状态已经涵盖了所有可能出现的情况。同时我们也可以得出几个个定理:

         a)S2态会进入T2态。(富裕堆不可能一次从2变为0,所以S2可能进入S1或T2,S1是必胜态,不可能让给对手,所以进入T2)

         b)T2态只能进入S2态或S1态。

         c)S1态可以任意选择进入S0或者T0。

         d)S0只能进入T0,T0只能进入S0。

    先来看问题一:

    这里S0态是必胜态,T0态是必败态。

    那么我们推理一下S2和T2的胜负性质,S2->T2->S2->....->T2->S1->T0->S0->....->T0->S0->T0(全部为0)。

    从上面我们可以看到如果自己是T2态那么一定会把S1态让给别人,又因为S1态可以任意选择S0或者T0态,所以S1态必胜。所以S2是必胜态,T2是必败态。

    接下来看问题二:

    这里S0态是必败态,T0态是必胜态。

    同理,我们推理一下S2和T2,S2->T2->S2->....->T2->S1->T0->S0->....->T0->S0->T0(全部为0)。

    我们会发现如果自己是T2态就会把S1态让给别人,S1态必胜。所以S2是必胜态,T2是必败态。(好吧感觉完全是重复了一遍- -、)

    这恰恰反应了在两个问题中“除了S0和T0的胜负性质发生了变化,S1、S2永远是必胜态,T2永远是必败态。”,所以除了每堆都是1时的特判,其他时候判断都是一样的。

    结论二(取完失败):

    1.如果每堆都是1,则判断奇偶,奇数后手胜,偶数先手胜。

    2.其他情况下如果nim-sum!=0则先手胜,反之后手胜。

    例题一:

    HDU 1850 Being a Good Boy in Spring Festivaly

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1850

    题目大意:有m堆牌,两个人先后取某堆中的任意(不少于一)张牌,最后取完者胜,问先手取胜第一次取牌有多少种取法。

    解题思路:典型的尼姆弈。关于有多少种胜利的取法,我们只需要判断一下在某一堆m张牌之后是否会使得a1^a2^a3.....^an=0成立,可以通过使用异或的性质,比如取第一堆,只要判断a2^a3.....^n<a1是否成立即可,把每一堆都遍历一遍就可找出答案。

    代码:

     1 #include<cstdio>
     2 int a[105];
     3 
     4 int main(){
     5     int n;
     6     while(scanf("%d",&n)&&n){
     7         int sum=0;//a[1]~a[n]的异或值 
     8         for(int i=1;i<=n;i++){
     9             scanf("%d",&a[i]);
    10             sum^=a[i];
    11         }
    12         int ans=0;
    13         for(int i=1;i<=n;i++){
    14             sum^=a[i];//a^b^b=a^0=a 
    15             if(sum<a[i])
    16                 ans++;
    17             sum^=a[i];
    18         }
    19         printf("%d
    ",ans);
    20     }
    21 }

     例题二:

    HDU 1907、HDU 2509

    题目链接:HDU1907HDU2509

    题目大意:两题除了输入输出一模一样,尼姆博弈变形,最后取完所有物品的人不是获胜而是失败,判断最后获胜的人。

    解题思路:就是我在开头介绍的问题二的类型,直接套结论:

             ①所有堆个数都为1时,若堆数为奇数则后手胜,若堆数为偶数则先手胜。

             ②其他情况,所有堆的异或值sum!=0那就先手胜,反之后手胜。

    代码:

     1 #include<cstdio>
     2 int a[50];
     3 
     4 int main(){
     5     int T;
     6     scanf("%d",&T);
     7     while(T--){
     8         int n;
     9         scanf("%d",&n);
    10         int sum=0,count=0;
    11         bool flag=false;
    12         for(int i=1;i<=n;i++){
    13             scanf("%d",&a[i]);
    14             if(a[i]==1)
    15                 count++; 
    16             sum^=a[i];
    17         }
    18         if(count==n)
    19             flag=(count%2==0);
    20         else 
    21             flag=(sum!=0);
    22         if(flag)
    23             printf("John
    ");
    24         else
    25             printf("Brother
    ");
    26     }
    27 } 
  • 相关阅读:
    小程序返回顶部top滚动
    创建对象的几种模式
    前端基础常识
    三行代码让页面中的所有元素增添不同颜色的外边框
    纯css制作小三角
    设计表单
    纯css制作三级菜单
    三栏-中栏流动布局
    三栏固定布局(为栏设定内边距和边框)
    ie8以下不兼容h5新标签的解决方法
  • 原文地址:https://www.cnblogs.com/fu3638/p/7463241.html
Copyright © 2020-2023  润新知