尼姆博弈

尼姆博弈

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

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

更严谨的推导是: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\n",count);
37     }
38 }

 

 

posted @ 2018-09-27 17:45  *starry*  阅读(300)  评论(0编辑  收藏  举报