尼姆博弈

以下内容许多都是看这里的:http://blog.csdn.net/acm_cxlove/article/details/7854530

尼姆博弈(Nim Game):两个人要玩一个游戏,有n堆纸牌,每堆有若干张,每人轮流取物纸牌,每次可以从其中一堆中取出若干张(不能不取),谁最后取完所有纸牌谁就获胜。

尼姆博弈有一个关键的定理:nim-sum是所有堆的纸牌数的异或值,比如有3堆纸牌,数量分别为a1,a2,a3。如果a1^a2^a3=0(这里的^是异或的意思),则先手必败,反之先手可以获胜。

结论一(取完获胜):

1.可以通过计算所有堆的nim-sum,nim-sum!=0先手胜,反之后手胜。

2.可以用计算后的nim-sum分别与所有堆的元素进行异或操作,如果得到结果小于原来堆的元素,则为可选操作。

我们这里有两个问题:

     一、有n堆纸牌,每堆有若干张,轮流取,每次可以从某一堆取若干张,谁取完就获胜。经典的尼姆博弈。

      二、有n堆纸牌,每堆有若干张,轮流取,每次可以从某一堆取若干张,谁取完就输掉。变形,把胜负条件反转一下。

我们接下来分析一下,设数量为1的堆为孤单堆,数量大于1的为充裕堆。令sum=a1^a2^...an。

     设置状态①S0态,sum!=0&&充裕堆=0(奇数个为孤单堆)。

     ②T0态,sum=0&&充裕堆=0(偶数个孤单堆)。

     ③S1态:充裕堆=1,必胜态(无论问题一还是二都是必胜,可以通过对富裕堆取得只剩一张牌或者全部取完,改变剩余n-1个孤单堆的奇偶性)。

     ④S2态,sum!=0&&充裕堆>=2。

     ⑤T2态,sum=0&&充裕堆>=2。

以上5种状态已经涵盖了所有可能出现的情况。同时我们也可以得出几个个定理:

     a)S2态会进入T2态。(富裕堆不可能一次从2变为0,所以S2可能进入S1或T2,S1是必胜态,不可能让给对手,所以进入T2)

     b)T2态只能进入S2态或S1态。

     c)S1态可以任意选择进入S0或者T0。

     d)S0只能进入T0,T0只能进入S0。

先来看问题一:

这里S0态是必胜态,T0态是必败态。

那么我们推理一下S2和T2的胜负性质,S2->T2->S2->....->T2->S1->T0->S0->....->T0->S0->T0(全部为0)。

从上面我们可以看到如果自己是T2态那么一定会把S1态让给别人,又因为S1态可以任意选择S0或者T0态,所以S1态必胜。所以S2是必胜态,T2是必败态。

接下来看问题二:

这里S0态是必败态,T0态是必胜态。

同理,我们推理一下S2和T2,S2->T2->S2->....->T2->S1->T0->S0->....->T0->S0->T0(全部为0)。

我们会发现如果自己是T2态就会把S1态让给别人,S1态必胜。所以S2是必胜态,T2是必败态。(好吧感觉完全是重复了一遍- -、)

这恰恰反应了在两个问题中“除了S0和T0的胜负性质发生了变化,S1、S2永远是必胜态,T2永远是必败态。”,所以除了每堆都是1时的特判,其他时候判断都是一样的。

结论二(取完失败):

1.如果每堆都是1,则判断奇偶,奇数后手胜,偶数先手胜。

2.其他情况下如果nim-sum!=0则先手胜,反之后手胜。

例题一:

HDU 1850 Being a Good Boy in Spring Festivaly

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1850

题目大意:有m堆牌,两个人先后取某堆中的任意(不少于一)张牌,最后取完者胜,问先手取胜第一次取牌有多少种取法。

解题思路:典型的尼姆弈。关于有多少种胜利的取法,我们只需要判断一下在某一堆m张牌之后是否会使得a1^a2^a3.....^an=0成立,可以通过使用异或的性质,比如取第一堆,只要判断a2^a3.....^n<a1是否成立即可,把每一堆都遍历一遍就可找出答案。

代码:

 1 #include<cstdio>
 2 int a[105];
 3 
 4 int main(){
 5     int n;
 6     while(scanf("%d",&n)&&n){
 7         int sum=0;//a[1]~a[n]的异或值 
 8         for(int i=1;i<=n;i++){
 9             scanf("%d",&a[i]);
10             sum^=a[i];
11         }
12         int ans=0;
13         for(int i=1;i<=n;i++){
14             sum^=a[i];//a^b^b=a^0=a 
15             if(sum<a[i])
16                 ans++;
17             sum^=a[i];
18         }
19         printf("%d
",ans);
20     }
21 }

 例题二:

HDU 1907、HDU 2509

题目链接:HDU1907HDU2509

题目大意:两题除了输入输出一模一样,尼姆博弈变形,最后取完所有物品的人不是获胜而是失败,判断最后获胜的人。

解题思路:就是我在开头介绍的问题二的类型,直接套结论:

         ①所有堆个数都为1时,若堆数为奇数则后手胜,若堆数为偶数则先手胜。

         ②其他情况,所有堆的异或值sum!=0那就先手胜,反之后手胜。

代码:

 1 #include<cstdio>
 2 int a[50];
 3 
 4 int main(){
 5     int T;
 6     scanf("%d",&T);
 7     while(T--){
 8         int n;
 9         scanf("%d",&n);
10         int sum=0,count=0;
11         bool flag=false;
12         for(int i=1;i<=n;i++){
13             scanf("%d",&a[i]);
14             if(a[i]==1)
15                 count++; 
16             sum^=a[i];
17         }
18         if(count==n)
19             flag=(count%2==0);
20         else 
21             flag=(sum!=0);
22         if(flag)
23             printf("John
");
24         else
25             printf("Brother
");
26     }
27 } 
原文地址:https://www.cnblogs.com/fu3638/p/7463241.html