尼姆博奕

有三堆物品,两人轮流从某一堆任取若干(大于0),最后取光者获胜。

我们用(a,b,c)表示三堆的状态,其有三种奇异局势:

  1. (0,0,0)时,即无论谁面对(0,0,0),其必输。
  2. (0,n,n)时,自己在某一堆拿走k(k ≤ n)个物品,不论k为多少,对方只要在另一堆拿走k个物品,最后自己都将面临(0,0,0)的局势,必败。
  3. (1,2,3)时,也是必败,无论自己如何拿,接下来对手都可以把局势变为(0,n,n)的情形。

这种奇异局势有特点,它们可以和二进制联系在一起:

  将这三堆物品的个数按照其二进制进行按位异或(⊕):(以1,2,3为例)

  1 =01
  2 =10
  3 =11 
  —————
  0 =00 

  对于任意的奇异局势,其结果都是0。

那么对于一个非奇异局势,如何变成奇异局势?

假设a<b<c,只需将c=a⊕b。因为a⊕b⊕(a⊕b)=(a⊕b)⊕(a⊕b)=0,即只需要将c进行c-(a⊕b)即可。

推广:

有n堆若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

假设有k堆,每堆的个数为:N1,N2,……,Nk。

其二进制为:

  N1=an…a2a1

  N2=bn…b2b1

  ……

  Nk=kn…k2k1

定义:尼姆博弈是平衡的当且仅当:

  an⊕bn⊕…⊕kn=0 

  ……

  a1⊕b1⊕…⊕k1=0

Bouton定理:先手能够在非平衡尼姆博弈中取胜,而后手能够在平衡的尼姆博弈中取胜。

根据是否是平衡尼姆博弈判断先手胜还是后手胜利

C代码:

#include <cstdio>
using namespace std;
int main(){
    int m,ans,n;
    while(~scanf("%d",&m)){
        n=ans=0;
        while(m--)
            scanf("%d",&n),ans^=n,printf("ans=%d
",ans);
        if(ans)printf("First
");
        else printf("Second
");
    }
}

例题

桌子上有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
#include<stdio.h>
int main()
{
    int x,m,a[110],i,ans;
    while(scanf("%d",&m),m)
    {
         x=ans=0;
         for(i=0;i<m;i++)
         {
                scanf("%d",&a[i]);
                x^=a[i];
         }
         for(i=0;i<m;i++)
         ans+=(a[i]>(x^a[i]));//a,b,c  ---因为a⊕b=a⊕b⊕c⊕c=x⊕c, 所以将所有的堆数进行异或得出x,然后再看哪一堆的个数比除其之外所有堆数的异或结果x^a[i]大,即为一种方案。
         printf("%d
",ans);
    }
    return 0;
}
 
原文地址:https://www.cnblogs.com/LMIx/p/10700274.html