有三堆物品,两人轮流从某一堆任取若干(大于0),最后取光者获胜。
我们用(a,b,c)表示三堆的状态,其有三种奇异局势:
- (0,0,0)时,即无论谁面对(0,0,0),其必输。
- (0,n,n)时,自己在某一堆拿走k(k ≤ n)个物品,不论k为多少,对方只要在另一堆拿走k个物品,最后自己都将面临(0,0,0)的局势,必败。
- (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; }