博弈论之尼姆博弈

尼姆博弈

母题:有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判输。

我们首先以一堆为例: 假设现在只有一堆石子,你的最佳选择是将所有石子全部拿走,那么你就赢了。

如果是两堆:假设现在有两堆石子且数量不相同,那么你的最佳选择是取走多的那堆石子中多出来的那几个,使得两堆石子数量相同,这样,不管另一个怎么取,你都可以在另一堆中和他取相同的个数,这样的局面你就是必胜。比如有两堆石子,第一堆有3个,第二堆有5个,这时候你要拿走第二堆的三个,然后两堆就都变成了3个,这时你的对手无论怎么操作,你都可以“学”他,比如他在第一堆拿走两个,你就在第二堆拿走两个,这样你就是稳赢的。

如果是多堆:道理同上,如果你能把局面变成一个不管对手取多少,你都可以复制他的取法的局面,你就赢了。

所以可以得出如下结论:

一个状态是必败状态当且仅当它的所有后继都是必胜状态
一个状态是必胜状态当且仅当它至少有一个后继是必败状态

有了这两个规则,就可以用递推的方法判断整个状态图的每一个结点都是必胜还是必败状态。
这里引入L . Bouton在1902年给出的定理:状态(x1,x2,x3)为必败状态当且仅当x1 XOR x2 XOR x3=0,这里的XOR是二进制的逐位异或操作,也成Nim和。

/*这个定理可以用数学归纳法来证明:
首先,如果每堆都没有石子,显然先手败,否则根据前面介绍的规则,组要证明两个结论:
1. 对于必胜状态,一定有一个后继状态是必败的
  **证明:**
  假设Nim和为X>0,且X=二进制表示最左边的1在第k位,则一定存在一个该位为1的堆。设这堆石子的数量为Y,则只需要吧它拿成Z=Y xor X个石子,得到的状态就是必败状态。
  为什么呢?我们需要证明两个结论:Z  < Y,以及新的Nim和等于0.注意,Y和X异或之后第K位变成了0,且第K为左边那些位均不变,因此Z一定比Y小;所有堆的Nim和原先等于X,现在Y变成了Z,新的Nim和等于X xor Y xor Z=X xor Y xor (Y xor X)=0.
2.对于必败状态,所有的后继状态都是必胜的
**证明:**
由于只能改变一堆火柴,不管修改它的哪一位,Nim和对应的哪一位一定部位0,因此不可能是必败状态。*/

上面那种是比较正规的证明过程,看不懂不要紧,给你几个具体的实例就懂了,上面的证明看懂看不懂没关系
加入有四堆石子(3,5,8,9),将所有数字按二进制表示:
3=0011
5=0101
7=0111
9=1001
然后这几个数字的Nim和为:
0 0 1 1
0 1 0 1
0 1 1 1
1 0 0 1
————
1 0 0 0=8!=0
所以这四个状态的Nim和为8,所以这个状态是先手胜。
我们模拟一下这个比赛的过程:刚开始时我只要把局面变成(3,5,7,1),然后因为这几个数字的二进制变成如下状态:
0 0 1 1
0 1 0 1
0 1 1 1
0 0 0 1
不管他取多少,你都可以按照二进制来在其余堆中复制他的取法,或者按几次分别取走,这时你就是必胜态。
这里贴上我的模板:

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int n,sum=0,num;
    scanf("%d",&n);
    for(int i=0;i<n;i++) {
        scanf("%d",&num);
        sum^=num;
    }
    if(sum!=0)
        printf("先手胜");
    else printf("后手胜");
    return 0;
}

例题:HDU 1849
Problem Description
大学时光是浪漫的,女生是浪漫的,圣诞更是浪漫的,但是Rabbit和Grass这两个大学女生在今年的圣诞节却表现得一点都不浪漫:不去逛商场,不去逛公园,不去和AC男约会,两个人竟然猫在寝食下棋……
说是下棋,其实只是一个简单的小游戏而已,游戏的规则是这样的:
1、棋盘包含1*n个方格,方格从左到右分别编号为0,1,2,…,n-1;
2、m个棋子放在棋盘的方格上,方格可以为空,也可以放多于一个的棋子;
3、双方轮流走棋;
4、每一步可以选择任意一个棋子向左移动到任意的位置(可以多个棋子位于同一个方格),当然,任何棋子不能超出棋盘边界;
5、如果所有的棋子都位于最左边(即编号为0的位置),则游戏结束,并且规定最后走棋的一方为胜者。

对于本题,你不需要考虑n的大小(我们可以假设在初始状态,棋子总是位于棋盘的适当位置)。下面的示意图即为一个1*15的棋盘,共有6个棋子,其中,编号8的位置有两个棋子。

示意图

大家知道,虽然偶尔不够浪漫,但是Rabbit和Grass都是冰雪聪明的女生,如果每次都是Rabbit先走棋,请输出最后的结果。

Input
输入数据包含多组测试用例,每个测试用例占二行,首先一行包含一个整数m(0<=m<=1000),表示本测试用例的棋子数目,紧跟着的一行包含m个整数Ki(i=1…m; 0<=Ki<=1000),分别表示m个棋子初始的位置,m=0则结束输入。

Output
如果Rabbit能赢的话,请输出“Rabbit Win!”,否则请输出“Grass Win!”,每个实例的输出占一行。

Sample Input
2
3 5
3
3 5 6
0

Sample Output
Rabbit Win!
Grass Win!

也是一道典型题,附上AC代码:

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int n;
    while(~scanf("%d",&n)&&n)
    {
        int sum=0,num;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&num);
            sum^=num;
        }
        if(sum)
            printf("Rabbit Win!\n");
        else
            printf("Grass Win!\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值