浅谈Nim博弈 附hdu 2176 取(m堆)石子游戏 ,hdu 1850 Being a Good Boy in Spring Festival

                                                                          浅谈Nim博弈                         

最近两天学的Nim博弈,一些心得拿出来给大家分享一下,也算是记录一下自己的学习心得方便今后复习。本人水平有限,如有错误或不妥的地方,还请各位大牛多多指教(^_^)

尼姆博弈模型一般是这样的:

有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少,取一个,多者不限,最后取光者得胜。给你三堆石头中的石头数:a,b,c,怎么样判断先手胜还是后手胜。

分析:

1.证明:

首先我们先自己模拟两个简单的例子, 三堆石子的数目分别是(0,2,2),我们可以枚举发现无论怎么走,先手都会输,另外一种情况,三堆石子的数目分别是(1,2,3),依然是先手输,因为无论先手怎么走,后手都可以将局势变成(0,n,n)这种局势。

那么我们再将局势推广到(0,n,n)的情况,发现如果先手取两个不为0的堆中的任何一个堆中的任何数量的石子,后手都可以对另外一个堆采取相同的操作,使局势依旧保持在(0,n,n)的状态,一直保持这样,直到最后先手不得不将一个堆中的石子全部取玩,那么后手马上可以将另外一个堆中剩余的石子取光,所以还是先手必胜。

那么,也就是说我们现在知道有三个奇异局势,分别是(0,0,0),(1,2,3),(0,n,n),那这三个局势看起来似乎没有什么联系。

真的没有吗?当然不可能,经过计算之后我们会发现这对三个奇异局势(a,b,c)都有a ^ b ^ c = 0。

那么也就是说这可能是奇异局势的特征咯,到底是不是没关系,我们可以证明一下:


首先定义P-position和N-position,其中P代表Previous,N代表Next。简单的说,就是P是必败态,N是必胜态

更严谨的定义是:1.无法进行任何移动的局面(也就是terminal position)是P-position;2.可以移动到P-position的局面是N-position;3.所有移动都导致N-position的局面是P-position。

这里对以上几点定义做出一点解释:第一点,无法进行任何移动的局面当然是必败的;第二点,如果当前是必胜态,那么当然要保证自己走完这一步之后留给对手的是必败态,所以可以移动到P态的是N态;第三点,自己无论现在这一步怎么走,走完这一步之后,留给对手的都是必胜态,这种局势当然是必败态。

那么现在拿出我们上面的假设:任何奇异局势(a,b,c)都有a ^ b ^ c = 0。

要证明这个假设,我们需要证明三个命题

1.根据该假设,所有terminal position都是P-position。

证明:因为terminal position只有一个,就是都是0的情况,那么不管多少个0异或之后结果肯定还是0。

2.根据该假设,当前被判为N-position的局势一定存在某种合法的移动,使得局势转变为P-position。

证明:对于任意一个局势(a1,a2........an)如果a1 ^ a2 ^...... ^ an != 0,那么设k = a1 ^ a2 ^...... ^ an ,从a1到an中一定存在一个数ai,它的二进制表示在k的最高位上为1,不然的话k二进制最高位上的1怎么来的。那么我现在设  s = ai ^ k = a1 ^ a2  ^.......^ a(i-1) ^ a(i+1) ^ .......an,则此时s二进制表示的最高位一定为0。(因为s ^ ai的最高位为1)所以, ai  ^ k < ai成立,所以我们设ai '  = ai  ^ k,则此时a1^a2^...^ai ' ^...^an = ai ' ^ (ai ^ k) = (ai ^ k)^(ai ^ k)= 0。所以存在一个合法的移动使得N-position转变为P-position局势。

3.根据该假设,当前被判断为P-position的局面无法移动到某个P-position。

证明:假设将ai改变成ai '能使得a1^a2^...^ai^...^an = a1^a2^...^ai ' ^...^an = 0,那么ai = ai ',而这种移动是不合法的。所以,当前被判断为P-position的局面无法移动到某个P-position。

那么我们现在就证明了对任意奇异局势(a1,a2,....an)都有a1^a2^...^an = 0的特征,同时,也将尼姆博弈模型从3堆推广到n堆。其实,这就是Bouton定理。

Bouton定理:状态(x1,x2,x3)为必败状态当且仅当x1^x2^x3=0,称为Nim和。

好了,现在证明完了,我们应该考虑如果在Nim博弈中获胜了,换句话说,在双方都采取最优策略的情况下,如何在己方是必胜态的情况下进行操作。

假定我们现在为先手,且局势为必胜局势。也就是a1 ^ a2 ^ ..... ^ an != 0,那么现在我们要将其中一个数ai减小为ai ',使得a1^a2^...^ai ' ^...^an = 0。首先,我们设k =a1^a2^...^ai^...^an,s = ai ^ k,那么问题就转化成了要找到一个 ai ' ,使得s ^ ai ' = 0,即 ai ’ = ai ^ k,再从数量为ai 的那堆中取出数量为ai - (ai ^ k)的数量的石头就可以了。

接下来附上两道例题,恰好解决的就是这个问题

例题一:

Being a Good Boy in Spring Festival

Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 3974    Accepted Submission(s): 2347


Problem Description
一年在外 父母时刻牵挂
春节回家 你能做几天好孩子吗
寒假里尝试做做下面的事情吧

陪妈妈逛一次菜场
悄悄给爸爸买个小礼物
主动地 强烈地 要求洗一次碗
某一天早起 给爸妈用心地做回早餐

如果愿意 你还可以和爸妈说
咱们玩个小游戏吧 ACM课上学的呢~

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

Input
输入数据包含多个测试用例,每个测试用例占2行,首先一行包含一个整数M(1
 

Output
如果先手的人能赢,请输出他第一步可行的方案数,否则请输出0,每个实例的输出占一行。
 

Sample Input
 
   
3 5 7 9 0
 

Sample Output
 
   
1
 

Author
lcy
 

Source
ACM Short Term Exam_2007/12/13

#include
#include
#include
#include
using namespace std;
int main()
{
    int n,i,sum,cnt;
    int Nim[105];
    while(scanf("%d",&n) && n)
    {
        cnt=0;
        sum=0;
        for(i=0;i(sum^Nim[i]))//如上所述,判断是否存在ai > ai ^ k,如果存在,对该堆进行操作。
                cnt++;
        }
        printf("%d\n",cnt);
    }
    return 0;
}
例题二:

取(m堆)石子游戏

Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1493    Accepted Submission(s): 869


Problem Description
m堆石子,两人轮流取.只能在1堆中取.取完者胜.先取者负输出No.先取者胜输出Yes,然后输出怎样取子.例如5堆 5,7,8,9,10先取者胜,先取者第1次取时可以从有8个的那一堆取走7个剩下1个,也可以从有9个的中那一堆取走9个剩下0个,也可以从有10个的中那一堆取走7个剩下3个.
 

Input
输入有多组.每组第1行是m,m<=200000. 后面m个非零正整数.m=0退出.
 

Output
先取者负输出No.先取者胜输出Yes,然后输出先取者第1次取子的所有方法.如果从有a个石子的堆中取若干个后剩下b个后会胜就输出a b.参看Sample Output.
 

Sample Input
 
   
2 45 45 3 3 6 9 5 5 7 8 9 10 0
 

Sample Output
 
   
No Yes 9 5 Yes 8 1 9 0 10 3
 

Author
Zhousc
 

Source
ECJTU 2008 Summer Contest
#include//和上一题几乎一样,只不过要输出具体的操作方案就是了。
#include
#include
#include
using namespace std;
int Nim[200050];
int main()
{
    int cnt, n;
    while(scanf("%d", &n) && n)
    {
        cnt = 0;
        for(int i=0; i= (Nim[i] ^ cnt))
                printf("%d %d\n", Nim[i], Nim[i] ^ cnt);
                ///对ai > ai ^ k的堆操作,使留下数量为ai ^ k,此时(ai ^ k) ^ (ai ^ k) = 0,使对手面对必败局势。
        }
    }
    return 0;
}


你可能感兴趣的:(博弈论)