zoukankan      html  css  js  c++  java
  • 尼姆博弈

    尼姆博弈

    一篇很好的博客:戳我戳我

    尼姆博弈的定义:有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。

    更严谨的推导是:1.无法进行任何移动的局面先手必败。2.可以移动到必败态的局面,先手必胜;3.所有移动都导致后手必胜的局面是先手必败。

    结论:对于一个Nim游戏的局面(a1,a2,...,an),它是先手必败态当且仅当a1^a2^...^an=0,其中^表示异或(xor)运算。

    Nim游戏的形象具体论述:

    Nim取子游戏是由两个人面对若干堆硬币(或石子)进行的游戏。设有k>=1堆硬币,各堆分别含有N1,N2,……NK枚硬币。游戏的目的就是选择最后剩下的硬币。游戏法则如下:
    1.两个游戏人交替进行游戏(游戏人I和游戏人II);
    2.当轮到每个游戏人取子时,选择这些堆中的一堆,并从所选的堆中取走至少一枚硬币(游戏人可以取走他所选堆中的全部硬币);
    3.当所有的堆都变成空堆时,最后取子的游戏人即为胜者。
    这个游戏中的变量是堆数k和各堆的硬币数N1,N2,……Nk。对应的组合问题是,确定游戏人I获胜还是游戏人II获胜以及两个游戏人应该如何取子才能保证自己获胜(获胜策略)。
    为了进一步理解Nim取子游戏,我们考查某些特殊情况。如果游戏开始时只有一堆硬币,游戏人I则通过取走所有的硬币而获胜。现在设有2堆硬币,且硬币数量分别为N1和N2。游戏人取得胜利并不在于N1和N2的值具体是多少,而是取决于它们是否相等。设N1!=N2,游戏人I从大堆中取走的硬币使得两堆硬币数量相等,于是,游戏人I以后每次取子的数量与游戏人II相等而最终获胜。但是如果N1= N2,则:游戏人II只要按着游戏人I取子的数量在另一堆中取相等数量的硬币,最终获胜者将会是游戏人II。这样,两堆的取子获胜策略就已经找到了。
    现在我们如何从两堆的取子策略扩展到任意堆数中呢?
    首先来回忆一下,每个正整数都有对应的一个二进制数,例如:57(10) à 111001(2) ,即:57(10)=25+24+23+20。于是,我们可以认为每一堆硬币数由2的幂数的子堆组成。这样,含有57枚硬币大堆就能看成是分别由数量为25、24、23、20的各个子堆组成。
    现在考虑各大堆大小分别为N1,N2,……Nk的一般的Nim取子游戏。将每一个数Ni表示为其二进制数(数的位数相等,不等时在前面补0):
    N= as…a1a0
    N= bs…b1b0
    ……
    N= ms…m1m0
    如果每一种大小的子堆的个数都是偶数,我们就称Nim取子游戏是平衡的,而对应位相加是偶数的称为平衡位,否则称为非平衡位。因此,Nim取子游戏是平衡的,当且仅当:

    a+ bs + … + ms 是偶数

    ……

    a+ b+ … + m是偶数

    a+ b0 + … + m0是偶数

    于是,我们就能得出获胜策略:
    游戏人I能够在非平衡取子游戏中取胜,而游戏人II能够在平衡的取子游戏中取胜。
    我们以一个两堆硬币的Nim取子游戏作为试验。设游戏开始时游戏处于非平衡状态。这样,游戏人I就能通过一种取子方式使得他取子后留给游戏人II的是一个平衡状态下的游戏,接着无论游戏人II如何取子,再留给游戏人I的一定是一个非平衡状态游戏,如此反复进行,当游戏人II在最后一次平衡状态下取子后,游戏人I便能一次性取走所有的硬币而获胜。而如果游戏开始时游戏牌平衡状态,那根据上述方式取子,最终游戏人II能获胜。
    下面应用此获胜策略来考虑4-堆的Nim取子游戏。其中各堆的大小分别为7,9,12,15枚硬币。用二进制表示各数分别为:0111,1001,1100和1111。于是可得到如下一表:
     

    23 = 8

    22 = 4

    21 = 2

    20 = 1

    大小为7的堆
    0
    1
    1
    1
    大小为9的堆
    1
    0
    0
    1
    大小为12的堆
    1
    1
    0
    0
    大小为15的堆
    1
    1
    1
    1
    由Nim取子游戏的平衡条件可知,此游戏是一个非平衡状态的取子游戏,因此,游戏人I在按获胜策略进行取子游戏下将一定能够取得最终的胜利。具体做法有多种,游戏人I可以从大小为12的堆中取走11枚硬币,使得游戏达到平衡(如下表),
     

    23 = 8

    22 = 4

    21 = 2

    20 = 1

    大小为7的堆
    0
    1
    1
    1
    大小为9的堆
    1
    0
    0
    1
    大小为12的堆
    0
    0
    0
    1
    大小为15的堆
    1
    1
    1
    1
    之后,无论游戏人II如何取子,游戏人I在取子后仍使得游戏达到平衡。
    同样的道理,游戏人I也可以选择大小为9的堆并取走5枚硬币而剩下4枚,或者,游戏人I从大小为15的堆中取走13枚而留下2枚。

    归根结底,Nim取子游戏的关键在于游戏开始时游戏处于何种状态(平衡或非平衡)和第一个游戏人是否能够按照取子游戏的获胜策略来进行游戏。

    Being a Good Boy in Spring Festival

     HDU - 1850 

     

    下面是一个二人小游戏:桌子上有M堆扑克牌;每堆牌的数量分别为Ni(i=1…M);两人轮流进行;每走一步可以任意选择一堆并取走其中的任意张牌;桌子上的扑克全部取光,则游戏结束;最后一次取牌的人为胜者。 
    现在我们不想研究到底先手为胜还是为负,我只想问大家: 
    ——“先手的人如果想赢,第一步有几种选择呢?” 

    Input输入数据包含多个测试用例,每个测试用例占2行,首先一行包含一个整数M(1<M<=100),表示扑克牌的堆数,紧接着一行包含M个整数Ni(1<=Ni<=1000000,i=1…M),分别表示M堆扑克的数量。M为0则表示输入数据的结束。 
    Output如果先手的人能赢,请输出他第一步可行的方案数,否则请输出0,每个实例的输出占一行。 
    Sample Input

    3
    5 7 9
    0

    Sample Output

    1
    题解:

    这里是对n堆的牌数去异或,如果值为0则表示必败。题目问我们第一布有哪几种方法胜利。

    即就是第一步能够给对手构建多少个必败点。

    由于一次只能对一堆排进行操作,假设我 操作第i堆牌(a张),抽出x张。

    那么其余n-1堆牌的异或值是固定为b.

    那么 (a - x)^ b == 0 时,对手必败。

    到此可能有人像我一样觉得必须历遍所有a求出那个值x满足条件。其实不必要

    由上式可知x只有唯一取值

     

    而且 一个数 与 b 异或等于 0 即表明 这个数等于b.

    所以反过来我们可以求出b, 令 a - x = b ;

    只要b满足 b < a; 即能构造出x使得 (a - x)^ b == 0 时,对手必败!

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 using namespace std;
     5 int a[110];
     6 int count;
     7 int n;
     8 int main()
     9 {
    10     while(~scanf("%d",&n)&&n)
    11     {
    12         int t=0;
    13         for(int i=0;i<n;i++)
    14         {
    15             scanf("%d",&a[i]);
    16             t^=a[i];
    17         }
    18         count=0;
    19         if(t)
    20         {
    21             
    22             for(int i=0;i<n;i++)
    23             {
    24                 int k=0;
    25                 for(int j=0;j<n;j++)
    26                 {
    27                     if(i!=j)
    28                     {
    29                         k^=a[j];
    30                     }
    31                 }
    32                 if(a[i]>=k)
    33                 count++;
    34             }    
    35         }
    36         printf("%d
    ",count);
    37     }
    38 }

     

  • 相关阅读:
    oc-25-id类型,
    oc-25-id类型
    [转贴]systemd 编写服务管理脚本
    [转贴]infoQ VSTS被拆成5个部分,以Azure DevOps服务形式推出
    Oracle 数据库备份启用压缩以及remap
    Support for SSL/TLS protocols on Windows
    微软自己的官网介绍 SSL 参数相关
    .NET4.0 加密通讯协议下TLS 的兼容支持问题.
    使用Word 进行UTF8 以及字符串编码的转换操作
    计算机管理以及设备管理的快捷打开方式
  • 原文地址:https://www.cnblogs.com/1013star/p/9714565.html
Copyright © 2011-2022 走看看