博弈论

博弈论

摘自https://blog.csdn.net/lgdblue/article/details/15809893

一、巴什博弈

    1、问题模型:只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个,最后取光者得胜。

    2、解决思路:当n=m+1时,由于一次最多只能取m个,所以无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,后者取胜,所以当一方面对的局势是n%(m+1)=0时,其面临的是必败的局势。所以当n=(m+1)*r+s,(r为任意自然数,s≤m)时,如果先取者要拿走s个物品,如果后取者拿走x(≤m)个,那么先取者再拿走m+1-x个,结果剩下(m+1)(r-1)个,以后保持这样的取法,那么先取者肯定获胜。总之,要保持给对手留下(m+1)的倍数,就能最后获胜。

    3、变形:条件不变,改为最后取光的人输。

    结论:当(n-1)%(m+1)==0时后手胜利。

    4、题目练习:HDOJ:2188 2149 1846  最后取光者得胜

题目

HDU-2188 //悼念512汶川大地震遇难同胞——选拔志愿者 HDU - 2188   //在线测评地址https://vjudge.net/problem/HDU-2188

HDU-2149 //Public Sale HDU - 2149   //在线测评地址https://vjudge.net/problem/HDU-2149

HDU-1846 //Brave Game HDU - 1846   //https://vjudge.net/problem/HDU-1846

题解

HDU-2188 //悼念512汶川大地震遇难同胞——选拔志愿者 HDU - 2188   //在线测评地址https://vjudge.net/problem/HDU-2188
//悼念512汶川大地震遇难同胞——选拔志愿者 HDU - 2188
//在线测评地址https://vjudge.net/problem/HDU-2188
//巴什博弈   最后取光者得胜
//样例通过,提交AC.2019-7-28 8:39
#include <stdio.h>
int main(){
    int n,m,c;
    scanf("%d",&c);
    while(c--){
        scanf("%d%d",&n,&m);
        if(n%(m+1))printf("Grass\n");//先手胜
        else printf("Rabbit\n");//先手负
    }
    return 0;
}

HDU-2149 //Public Sale HDU - 2149   //在线测评地址https://vjudge.net/problem/HDU-2149

//Public Sale HDU - 2149
//在线测评地址https://vjudge.net/problem/HDU-2149
//巴什博弈   最后取光者得胜
//样例通过,提交AC.2019-7-28 14:13
#include <stdio.h>
int main(){
    int m,n,i;
    while(scanf("%d%d",&m,&n)!=EOF){
        if(m%(n+1)==0)printf("none\n");
        else if(m/(n+1)==0){
            printf("%d",m);
            for(i=m+1;i<=n;i++)printf(" %d",i);
            printf("\n");
        }else printf("%d\n",m%(n+1));
    }
    return 0;
}

HDU-1846 //Brave Game HDU - 1846   //https://vjudge.net/problem/HDU-1846

//Brave Game HDU - 1846
//https://vjudge.net/problem/HDU-1846
//巴什博弈   最后取光者得胜
//样例通过,提交AC.2019-7-28 14:29
#include <stdio.h>
int main(){
    int c,n,m;
    scanf("%d",&c);
    while(c--){
        scanf("%d%d",&n,&m);
        if(n%(m+1))printf("first\n");
        else printf("second\n");
    }
    return 0;
}

二、威佐夫博奕

https://blog.csdn.net/qq_41311604/article/details/79980882此文关于威佐夫博奕讲解摘抄如下

威佐夫博弈(Wythoff's game)是指的这样一个问题:有两堆各若干个物品,两个人轮流从任意一堆中取出至少一个或者同时从两堆中取出同样多的物品,规定每次至少取一个,至多不限,最后取光者胜利。

我们用(a[k],b[k])    (a[k] ≤ b[k] ,k=0,1,2,......n)来表示两堆物品的数量,并且称这个为局势。

首先我们来从最简单的情况开始分析:

如果现在的局势是(0,0),很明显此时已经没有办法再取了,所以肯定是之前的人在上一局中取完了。

假设现在的局势是(1,2),那么先手只有四种取法。

(1) 如果先手取走“1”中的1个,那么后手就从“2”中取出2个,此时取完,所以后手胜利。

(2)如果先手取走“2”中的2个,那么后手取走“1”中的1个,此时取完,后手胜利。

(3)如果先手取走“2”中的1个,那么后手就在两堆中各取走1个,此时取完,后手胜利。

(4)如果先手在“1”和“2”各取走了1个,那么后手取走“2”中的1个,此时取完,后手胜利。

  由此可得,先手必输。

是不是觉得这个后手好厉害,无论先手怎么取,后手都会胜利。

在学习威佐夫博弈之前,我也是这样认为的。不过,当你继续看完这篇博客,你也会轻松获得胜利。

为了让大家更好地理解威佐夫博弈,我们继续来进行具体分析。

假设现在的局势是(3,5),首先根据上面分析的经验,我们知道先手肯定不能把任意一堆物品取完,这是因为每次可以从任意一堆取走任意个物品,那么后手就可以直接把另一堆取完,所以后手获胜。

所以我们这里就不分析那些情况,来分析其他的情况。

先看在一堆中取的情况:

(1) 假设先手在“3”中取1个,后手就可以在“5”中取走4个,这样就变成了(1,2)的局势,根据上面的分析,我们知道是先手输,后手获胜。

(2) 假设先手在“3”中取2个,后手就可以在 “5” 中取走3个,这样也变成了(1,2)的局势了,还是先手输,后手获胜。

(3)假设先手在“5”中取1个,后手就在 “3”和“5” 中各取走2个,这样又成了(1,2)的局势了,先手输,后手赢。

(4)假设先手在“5”中取2个,后手就在 “3”和“5” 中各取走3个,这样变成了(0,0)的局势,先手输,后手赢。

(5)假设先手在“5”中取3个,后手就在 “3”和“5” 中各取走1个,也变成了(1,2)的局势,先手输,后手胜利。

(6)假设先手在“5”中取4个,后手在“3”中取走1个,还是(1,2)的局势,先手输,后手赢。

我们发现上面列举的这几种局势,无论先手怎么取都是后手赢。

我们可以来找找那些先手必输局势的规律

第一个(0,0)

第二个(1,2)

第三个(3,5)

第四个(4 ,7)

第五个(6,10)

第六个 (8,13)

第七个 ( 9 , 15)

第八个 ( 11 ,18)

第n个(a[k],b[k])

我们把这些局势称为“奇异局势”

我们会发现他们的差值是递增的,分别是0,1,2,3,4,5,6,7......n

有兴趣的读者可以自己模拟一下过程进行验证

我们用数学方法分析发现这些局势的第一个值是未在前面出现过的最小的自然数。

继续分析我们会发现,每种奇异局势的第一个值总是等于当前局势的差值乘上1.618

我们都知道0.618是黄金分割率。而威佐夫博弈正好是1.618,这就是博弈的奇妙之处!

即 a[k] = (int)  ((b[k] - a[k])*1.618) 注:这里的int是强制类型转换,注意这不是简单的四舍五入,假如后面的值是3.9,转换以后得到的不是4而是3,也就是说强制int类型转换得到的是不大于这个数值的最大整数。

在编程题中,有些题目要求精度较高,我们可以用下述式子来表示这个值

1.618 = (sqrt(5.0) + 1) / 2

摘自https://blog.csdn.net/lgdblue/article/details/15809893

    1、问题模型:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

    2、解决思路:A:设(ai,bi)(ai ≤bi  ,i=0,1,2,…,n)表示两堆物品的数量并称其为局势,如果甲面对(0,0),那么甲已经输了,这种局势我们称为奇异局势。前几个奇异局势是:(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)。任给一个局势(a,b),如下公式判断它是不是奇异局势: ak =[k(1+√5)/2],bk= ak + k  (k=0,1,2,…,n 方括号表示取整函数)。(证明见百度百科https://baike.baidu.com/item/%E5%A8%81%E4%BD%90%E5%A4%AB%E5%8D%9A%E5%BC%88/19858256?fr=aladdin

    B:详见 http://www.freopen.com/?p=10512)

    3、满足上公式的局势性质:

    (1)任何自然数都包含在一个且仅有一个奇异局势中。

         由于ak是未在前面出现过的最小自然数,所以有ak > ak-1 ,而 bk= ak + k > ak-1 + k-1 = bk-1 > ak-1 。所以性质成立。

    (2)任意操作都可将奇异局势变为非奇异局势。

        若只改变奇异局势(ak,bk)的某一个分量,那么另一个分量不可能在其他奇异局势中,所以必然是非奇异局势。如果使(ak,bk)的两个分量同时减少,则由于其差不变,且不可能是其他奇异局势的差,因此也是非奇异局势

    (3)采用适当的方法,可以将非奇异局势变为奇异局势。

         摘自https://blog.csdn.net/wang3312362136/article/details/79303794

   a=ak,b<bk的取物情况,可以根据以下数据进行思考,希望读者能尽快弄懂.2019-7-28 21:27

第一个(0,0)

第二个(1,2)

第三个(3,5)

第四个(4 ,7)

第五个(6,10)

第六个 (8,13)

第七个 ( 9 , 15)

第八个 ( 11 ,18)

第n个(a[k],b[k])

4、结论:两个人如果都采用正确操作,那么面对非奇异局势,先拿者必胜;反之,则后拿者取胜。

    5、练习:poj 1067

摘自https://www.cnblogs.com/Wolfycz/p/8430991.html

题目

POJ-1067 //取石子游戏 POJ - 1067   //在线测评地址https://vjudge.net/problem/POJ-1067

题解

POJ-1067 //取石子游戏 POJ - 1067   //在线测评地址https://vjudge.net/problem/POJ-1067

//取石子游戏 POJ - 1067
//在线测评地址https://vjudge.net/problem/POJ-1067
//样例通过,提交AC.2019-7-28 21:53
#include <stdio.h>
#include <math.h>
int main(){
    int a,b,k,t;
    while(scanf("%d%d",&a,&b)!=EOF){
        if(a>b)t=a,a=b,b=t;//目标a<=b
        k=b-a;
        t=(int)((sqrt(5)+1)/2*k);
        if(t==a)printf("0\n");
        else printf("1\n");
    }
    return 0;
}

三、Fibonacci博弈

摘自https://www.cnblogs.com/henry-1202/p/9744678.html

题目:有n张纸牌,A,B两人轮流按照以下规则取牌。

规则一:A先取,但是不能在第一次将纸牌全部取完,而且至少要取一张;

规则二:每次所取纸牌张数必须大于或等于1,且小于等于对手刚取的纸牌张数的两倍。取到最后一张牌者为胜者。

输入纸牌的张数n,判断A是否必胜,如果必胜,输出”win”,否则输出”lose”。

问题分析:当n较小时,可以归纳如下:

⑴2张牌时,先拿的人必输;

⑵3张牌时,先拿的人必输;

⑶我们先看5张牌的情况,假如我们把取5张牌分成两个步骤:先取前面2张,再取后面3张,为什么可以这样分成两个步骤?因为后取者有这个权力!先者只能取第一张,后者可以取到第二张,这样,后者就必可以取到第5张牌,先者必输。

同样,如果是8张牌时,可以分为:先取前面3张,再取后面5张,后者胜,先者必输。

结论:⑴如果牌的张数n

是Fibonacci数时,先取牌者必败。 ⑵对所有非Fibonacci

数都是先取人必赢,反之,必败。
下面给出一般性证明:

假设n<=k

,且牌数为Fibonacci

数时,都是取牌者必输。

那么n=k+1

时,因为F(k+1)=F(k)+F(k−1),即要取完F(k+1)张牌,可以分成两步:先取完F(k−1)张牌,再取完F(k)张牌。对于F(k−1)张牌,先取A者输!意味着对于F(k)

张牌,A还得必须先取,所以A输。

那么,牌数为非Fibonacci

数时,先取牌者有没有必胜的策略呢?

引用一个定理:当一个数不是Fibonacci

数时,这个数必然等于若干个Fibonacci数之和,并且这些Fibonacci数在Fibonacci

数列中都不相邻。

比如:78=55+23=55+21+2

(其中55,21,2都是Fibonacci

数);

对于非Fibonacci

数a0,设f(n)是小于4a0的最大

Fibonacci$数。

a0=f(n)+…+f(i)+f(j)

,其中f(j)是式中最小的Fibonacci数,f(i)是第二小的Fibonacci数。由于f(i)、f(j)在Fibonacci数列中并不是相邻的,所以f(i)>2∗f(j)。所以先取者可以直接取走f(j)张牌,后取者无法一次取走f(i)张牌,f(i)是Fibonacci

数,由前面的分析,后取者必败。

结论:对所有非Fibonacci

数都是先取人必赢,反之,必败。

摘自https://blog.csdn.net/dgq8211/article/details/7602807

有一堆个数为n(n>=2)的石子,游戏双方轮流取石子,规则如下:

1)先手不能在第一次把所有的石子取完,至少取1颗;

2)之后每次可以取的石子数至少为1,至多为对手刚取的石子数的2倍。

约定取走最后一个石子的人为赢家,求必败态。

结论:当n为Fibonacci数的时候,必败。

f[i]:1,2,3,5,8,13,21,34,55,89……


用第二数学归纳法证明:

为了方便,我们将n记为f[i]。

1、当i=2时,先手只能取1颗,显然必败,结论成立。

2、假设当i<=k时,结论成立。

     则当i=k+1时,f[i] = f[k]+f[k-1]。

     则我们可以把这一堆石子看成两堆,简称k堆和k-1堆。

    (一定可以看成两堆,因为假如先手第一次取的石子数大于或等于f[k-1],则后手可以直接取完f[k],因为f[k] < 2*f[k-1])

     对于k-1堆,由假设可知,不论先手怎样取,后手总能取到最后一颗。下面我们分析一下后手最后取的石子数x的情况。

     如果先手第一次取的石子数y>=f[k-1]/3,则这小堆所剩的石子数小于2y,即后手可以直接取完,此时x=f[k-1]-y,则x<=2/3*f[k-1]。

     我们来比较一下2/3*f[k-1]与1/2*f[k]的大小。即4*f[k-1]与3*f[k]的大小,对两值作差后不难得出,后者大。

     所以我们得到,x<1/2*f[k]。

     即后手取完k-1堆后,先手不能一下取完k堆,所以游戏规则没有改变,则由假设可知,对于k堆,后手仍能取到最后一颗,所以后手必胜。

     即i=k+1时,结论依然成立。


那么,当n不是Fibonacci数的时候,情况又是怎样的呢?

这里需要借助“Zeckendorf定理”(齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和。

关于这个定理的证明,感兴趣的同学可以在网上搜索相关资料,这里不再详述。

分解的时候,要取尽量大的Fibonacci数。

比如分解85:85在55和89之间,于是可以写成85=55+30,然后继续分解30,30在21和34之间,所以可以写成30=21+9,

依此类推,最后分解成85=55+21+8+1。

则我们可以把n写成  n = f[a1]+f[a2]+……+f[ap]。(a1>a2>……>ap)

我们令先手先取完f[ap],即最小的这一堆。由于各个f之间不连续,则a(p-1) > ap  + 1,则有f[a(p-1)] > 2*f[ap]。即后手只能取f[a(p-1)]这一堆,且不能一次取完。

此时后手相当于面临这个子游戏(只有f[a(p-1)]这一堆石子,且后手先取)的必败态,即先手一定可以取到这一堆的最后一颗石子。

同理可知,对于以后的每一堆,先手都可以取到这一堆的最后一颗石子,从而获得游戏的胜利。
摘自https://blog.csdn.net/dgq8211/article/details/7602807

有一堆个数为n(n>=2)的石子,游戏双方轮流取石子,规则如下:

1)先手不能在第一次把所有的石子取完,至少取1颗;

2)之后每次可以取的石子数至少为1,至多为对手刚取的石子数的2倍。

约定取走最后一个石子的人为赢家,求必败态。

结论:当n为Fibonacci数的时候,必败。

f[i]:1,2,3,5,8,13,21,34,55,89……
用第二数学归纳法证明:

为了方便,我们将n记为f[i]。

1、当i=2时,先手只能取1颗,显然必败,结论成立。

2、假设当i<=k时,结论成立。

     则当i=k+1时,f[i] = f[k]+f[k-1]。

     则我们可以把这一堆石子看成两堆,简称k堆和k-1堆。

    (一定可以看成两堆,因为假如先手第一次取的石子数大于或等于f[k-1],则后手可以直接取完f[k],因为f[k] < 2*f[k-1])

     对于k-1堆,由假设可知,不论先手怎样取,后手总能取到最后一颗。下面我们分析一下后手最后取的石子数x的情况。

     如果先手第一次取的石子数y>=f[k-1]/3,则这小堆所剩的石子数小于2y,即后手可以直接取完,此时x=f[k-1]-y,则x<=2/3*f[k-1]。

     我们来比较一下2/3*f[k-1]与1/2*f[k]的大小。即4*f[k-1]与3*f[k]的大小,对两值作差后不难得出,后者大。

     所以我们得到,x<1/2*f[k]。

     即后手取完k-1堆后,先手不能一下取完k堆,所以游戏规则没有改变,则由假设可知,对于k堆,后手仍能取到最后一颗,所以后手必胜。

     即i=k+1时,结论依然成立。


那么,当n不是Fibonacci数的时候,情况又是怎样的呢?

这里需要借助“Zeckendorf定理”(齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和。

关于这个定理的证明,感兴趣的同学可以在网上搜索相关资料,这里不再详述。

分解的时候,要取尽量大的Fibonacci数。

比如分解85:85在55和89之间,于是可以写成85=55+30,然后继续分解30,30在21和34之间,所以可以写成30=21+9,

依此类推,最后分解成85=55+21+8+1。

则我们可以把n写成  n = f[a1]+f[a2]+……+f[ap]。(a1>a2>……>ap)

我们令先手先取完f[ap],即最小的这一堆。由于各个f之间不连续,则a(p-1) > ap  + 1,则有f[a(p-1)] > 2*f[ap]。即后手只能取f[a(p-1)]这一堆,且不能一次取完。

此时后手相当于面临这个子游戏(只有f[a(p-1)]这一堆石子,且后手先取)的必败态,即先手一定可以取到这一堆的最后一颗石子。

同理可知,对于以后的每一堆,先手都可以取到这一堆的最后一颗石子,从而获得游戏的胜利。

摘自https://blog.csdn.net/lgdblue/article/details/15809893

    1、问题模型:

     有一堆个数为n的石子,游戏双方轮流取石子,满足:

    (1)先手不能在第一次把所有的石子取完;

    (2)之后每次可以取的石子数介于1到对手刚取的石子数的2倍之间(包含1和对手刚取的石子数的2倍)。 约定取走最后一个石子的人为赢家。

    2、解决思路:

      当n为Fibonacci数时,先手必败。即存在先手的必败态当且仅当石头个数为Fibonacci数。

        证明:根据“Zeckendorf定理”(齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和。如n=83 = 55+21+5+2,我们看看这个分解有什么指导意义:假如先手取2颗,那么后手无法取5颗或更多,而5是一个Fibonacci数,那么一定是先手取走这5颗石子中的最后一颗,同理,接下去先手取走接下来的后21颗中的最后一颗,再取走后55颗中的最后一颗,那么先手赢。

        反证:如果n是Fibonacci数,如n=89:记先手一开始所取的石子数为y

        (1)若y>=34颗(也就是89的向前两项),那么一定后手赢,因为89-34=55=34+21<2*34。

        (2)y<34时剩下的石子数x介于55到89之间,它一定不是一个Fibonacci数,把x分解成Fibonacci数:x=55+f[i]+…+f[j],若,如果f[j]<=2y,那么对B就是面临x局面的先手,所以根据之前的分析,后手只要先取f[j]个即可,以后再按之前的分析就可保证必胜。

    3、练习题目:HDU-2516

摘自https://blog.csdn.net/acdreamers/article/details/8586135

题意:

一堆石子有n个,两人轮流取,先取者第1次可以取任意多个,但不能全部取完,以后每次取的石子数不能超过上次取子数的

2倍。取完者胜.先取者负输出"Second win".先取者胜输出"First win"。

分析:这个跟威佐夫博弈和取石子游戏有一个很大的不同点,就是游戏规则的动态化。后两者的规则中,每次可以取的石子

的策略集合是基本固定的,但是这次有规则2:一方每次可以取的石子数依赖于对手刚才取的石子数。

这个游戏叫做Fibonacci Nim,肯定和Fibonacci数列f[n]:1,2,3,5,8,13,21,34,55,89,… 有密切的关系。如果试

验一番之后,可以猜测:先手胜当且仅当n不是Fibonacci数,换句话说,必败态构成Fibonacci数列。

下面简单谈谈“先手败当且仅当n为Fibonacci数列”这一结论是怎么得来的。
这里要用到一个很有用的定理:任何正整数可以表示为若干个不连续的 Fibonacci 数之和。

这里定理涉及到数论,这里不做证明。下面只谈如何把一个正整数表示为若干个不连续的 Fibonacci 数之和。
比如,我们要分解83,注意到83被夹在55和89之间,于是把83可以写成83=55+28;然后再想办法分解28,28被夹在21和

34之间,于是28=21+7;依此类推 7=5+2,故83=55+21+5+2。
如果 n 是 Fibonacci 数,比如 n = 89。89前面的两个Fibonacci 数是34和55。如果先手第一次取的石子不小于34

颗,那么一定后手赢,因为 89 - 34 = 55 = 34 + 21 < 2*34,注意55是Fibonacci数。此时后手只要将剩下的全部

取光即可,此时先手必败。故只需要考虑先手第一次取得石子数 < 34 即可,于是剩下的石子数 x 介于 55 到 89 之

间,它一定不是一个 Fibonacci 数。于是我们把 x 分解成 Fibonacci 数:x = 55 + f[i] + … + f[j],其中

55 > f[i] > … > f[j],如果 f[j] ≤ 先手一开始所取石子数 y 的两倍,那么对后手就是面临 x 局面的先手,所以

根据之前的分析,后手只要先取 f[j] 个即可,以后再按之前的分析就可保证必胜。
下证:f[j] ≤ 2y

反证法:假设f[j]>2y,则 y < f[j]/2 = (f[j-1] + f[j-2])/2 < f[j-1]。而最初的石子数是个斐波那契数,即

n = f[k] = x + y < f[k-1] + f[i] + … + f[j] + f[j-1] ≤ f[k-1]+f[i]+f[i-1] ≤ f[k-1]+f[k-2] ≤

f[k] (注意第一个不等号是严格的),矛盾!f[j] ≤ 2y得证。
如果 n 不是 Fibonacci 数,比如n=83,我们看看这个分解有什么指导意义:假如先手取2颗,那么后手无法取5颗或更

多,而5是一个Fibonacci数,如果猜测正确的话,(面临这5颗的先手实际上是整个游戏的后手)那么一定是整个游戏的

先手取走这5颗石子中的最后一颗,而这个我们可以通过第二类归纳法来绕过,同样的道理,根据“先手败当且仅当n为

Fibonacci数列”,接下去先手取走接下来的后21颗中的最后一颗,再取走后55颗中的最后一颗,那么先手赢。

摘自https://www.cnblogs.com/Wolfycz/p/8430991.html

题目

HDU-2516 //取石子游戏 HDU - 2516   //在线测评地址https://vjudge.net/problem/HDU-2516

题解

HDU-2516 //取石子游戏 HDU - 2516   //在线测评地址https://vjudge.net/problem/HDU-2516

//取石子游戏 HDU - 2516
//在线测评地址https://vjudge.net/problem/HDU-2516
//样例通过,提交AC.2019-7-29 22:01
#include <stdio.h>
#define LL long long
LL f[50];
int main(){
    int i,n,k;
    f[1]=1,f[2]=2,f[3]=3;
    for(i=3;f[i-1]<=0x7fffffff;i++)//此处写成for(i=3;f[i]<=0x7fffffff;i++)查了好久
        f[i]=f[i-1]+f[i-2];
    k=i-1;
    while(scanf("%d",&n)&&n){
        for(i=1;i<=k;i++)
            if(f[i]>=n)break;
        if(f[i]==n)printf("Second win\n");
        else printf("First win\n");
    }
    return 0;
}

摘自https://blog.csdn.net/lgdblue/article/details/15809893

四、尼姆博弈

    1、问题模型:有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

    2、解决思路:用(a,b,c)表示某种局势,显证(0,0,0)是第一种奇异局势,无论谁面对奇异局势,都必然失败。第二种奇异局势是(0,n,n),只要与对手拿走一样多的物品,最后都将导致(0,0,0)。

      搞定这个问题需要把必败态的规律找出:(a,b,c)是必败态等价于a^b^c=0(^表示异或运算)。

      证明:(1)任何p(a,b,c)=0的局面出发的任意局面(a,b,c’);一定有p(a,b,c’)不等于0。否则可以得到c=c’。

          (2)任何p(a,b,c)不等于0的局面都可以走向 p(a,b,c)=0的局面

           (3)对于 (4,9,13) 这个容易验证是奇异局势

                 

           其中有两个8,两个4,两个1,非零项成对出现,这就是尼姆和为  零的本质。别人要是拿掉13里的8或者1,那你就拿掉对应的9  中的那个8或者1;别人要是拿        掉13里的4,你就拿掉4里的4;  别人如果拿掉13里的3,就把10作分解,然后想办法满 足非零项成对即可。

    3、推广一:如果我们面对的是一个非奇异局势(a,b,c),要如何变为奇异局势呢?假设 a < b< c,我们只要将 c 变为 a^b,即可,因为有如下的运算结果: a^b^(a^b)=(a^a)^(b^b)=0^0=0。要将c 变为a^b,只从 c中减去 c-(a^b)

    4、推广二:当石子堆数为n堆时,则推广为当对每堆的数目进行亦或之后值为零是必败态。

    5、练习:hdoj 1849

摘自https://www.cnblogs.com/Wolfycz/p/8430991.html

题目

HDU-1849 //Rabbit and Grass HDU - 1849   //在线测评地址https://vjudge.net/problem/HDU-1849

题解

HDU-1849 //Rabbit and Grass HDU - 1849   //在线测评地址https://vjudge.net/problem/HDU-1849

//Rabbit and Grass HDU - 1849
//在线测评地址https://vjudge.net/problem/HDU-1849
//Nim
//样例通过,提交AC.2019-7-30 15:57
#include <stdio.h>
int main(){
    int m,ans,x;
    while(scanf("%d",&m)&&m){
        ans=0;
        while(m--){
            scanf("%d",&x);
            ans^=x;
        }
        if(ans==0)printf("Grass Win!\n");//先手必败
        else printf("Rabbit Win!\n");
    }
    return 0;
}

五、公平组合博弈(Impartial Combinatori Games)

摘自http://keyblog.cn/article-47.html

SG博弈

开门见山,我的理解:SG博弈算法可以看成是一个图的搜索算法,即把每个可能的点抽象成图的点,而这些点有两个状态(0非0,对应必输必赢)

1.那么先抽象介绍下

一个公平游戏可以抽象地用一个有向无环图来表示,这个图中每个点都对应这一个状态,每条有向边代表从一个状态到另一个状态的合法操作。

我们可以想象一个硬币最初放在某个点上,然后两个玩家轮流将其从当前的点移动到它的后继点。当硬币移动到汇点时游戏结束,汇点是一个没有出度的点,最后一个需要操作的玩家就是胜者。

P- 和 N-状态

如果双方都按照最佳策略进行游戏,我们可以将游戏中的每个状态依据其是先手必胜还是后手必胜分类。

一个先手胜状态被认为是一个N-状态(因为下一个玩家即将获胜),一个后手胜状态被认为是一个P-状态(因为前一个玩家即将获胜)

P-和N-状态归纳性地描述如下:

一个点v是P-状态当且仅当它的所有后继都为N-状态

一个点v是N-状态当且仅当它的一些后继是P-状态

这个归纳从汇点开始,汇点是P-状态因为它显然满足P-状态的要求。

游戏的P-和N-状态的信息提供了它的必胜策略。如果轮到我们且游戏处在一个N-状态,我们应该转移到一个P-状态。接着我们的对手就会被迫进入N-状态,依此类推。我们最终会移入一个汇点并获得胜利。

摘自https://blog.csdn.net/bestsort/article/details/88197959

必胜点和必败点的概念

    必败点(P点) 前一个(previous player)选手将取胜的点称为必败点
    必胜点(N点) 下一个(next player)选手将取胜的点称为必胜点

比如现在数字已经为18了,那么当前操作人只要给数字+3则必胜,我们就把在此位置称为必胜点(正常操作情况下,别杠说都18偏要+2。。。。)
必胜点和必败点的性质:
- 所有的终结点都是必败点
- 从任何必胜点操作,至少有一种方式进入必败点
- 无论如何操作, 从必败点都只能进入必胜点.
Sprague-Grundy(SG)定理

游戏和的SG函数等于各个游戏SG函数的Nim和。这样就可以将每一个子游戏分而治之,从而简化了问题。而Bouton定理就是Sprague-Grundy定理在Nim游戏中的直接应用,因为单堆的Nim游戏 SG函数满足 SG(x) = x。

    Nim和 : 各个数相异或的结果

SG函数

先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于任意状态 x , 定义 SG(x) = mex(S),其中 S
S是 xx 后继状态的SGSG函数值的集合。如 x 有三个后继状态分别为 SG(a),SG(b),SG(c)SG(a),SG(b),SG(c),那么SG(x)=mexSG(x)=mex{SG(aSG(a,SG(b)SG(b),SG(c)SG(c)}。 这样 集合SS 的终态必然是空集,所以SG函数的终态为 SG(x)=0

SG(x)=0,当且仅当 x 为必败点P时。

取石子问题

有1堆n个的石子,每次只能取{ 1, 3, 4 }个石子,先取完石子者胜利,那么各个数的SG值为多少?

SG[0]=0,f[]={1,3,4},

x=1 时,可以取走1 - f{1}个石子,剩余{0}个,所以 SG[1] = mex{ SG[0] }= mex{0} = 1;

x=2 时,可以取走2 - f{1}个石子,剩余{1}个,所以 SG[2] = mex{ SG[1] }= mex{1} = 0;

x=3 时,可以取走3 - f{1,3}个石子,剩余{2,0}个,所以 SG[3] = mex{SG[2],SG[0]} = mex{0,0} =1;

x=4 时,可以取走4- f{1,3,4}个石子,剩余{3,1,0}个,所以 SG[4] = mex{SG[3],SG[1],SG[0]} = mex{1,1,0} = 2;

x=5 时,可以取走5 - f{1,3,4}个石子,剩余{4,2,1}个,所以SG[5] = mex{SG[4],SG[2],SG[1]} =mex{2,0,1} = 3;

 

 

以此类推…

       x 0 1 2 3 4 5 6 7 8…

SG[x] 0 1 0 1 2 3 2 0 1…

由上述实例我们就可以得到SG函数值求解步骤,那么计算1~n的SG函数值步骤如下:

1、使用 数组f 将 可改变当前状态 的方式记录下来。

2、然后我们使用 另一个数组 将当前状态x 的后继状态标记。

3、最后模拟mex运算,也就是我们在标记值中 搜索 未被标记值 的最小值,将其赋值给SG(x)。

4、我们不断的重复 2 - 3 的步骤,就完成了 计算1~n 的函数值。
模板如下:

//f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在getSG之前先预处理
//SG[]:0~n的SG函数值
//S[]:为x后继状态的集合
int f[N],SG[MAXN],S[MAXN];
void  getSG(int n){
    int i,j;
    memset(SG,0,sizeof(SG));
    //因为SG[0]始终等于0,所以i从1开始
    for(i = 1; i <= n; i++){
        //每一次都要将上一状态 的 后继集合 重置
        memset(S,0,sizeof(S));
        for(j = 0; f[j] <= i && j <= N; j++)
            S[SG[i-f[j]]] = 1;  //将后继状态的SG函数值进行标记
        for(j = 0;; j++) if(!S[j]){   //查询当前后继状态SG值中最小的非零值
            SG[i] = j;
            break;
        }
    }
}

其实不难发现,Nim游戏就是一个很典型的用SG定理解决的问题,因为Nim游戏在一堆n个石子中可以取1-n个石子,所以单独这一堆石子的SG值为mex(n−1,n−2,n−3,...,n−n)=n
mex(n−1,n−2,n−3,...,n−n)=n,根据SG定理,每一堆石子总数相互异或即为答案

摘自https://blog.csdn.net/wang57389675/article/details/45895473

对于一个游戏可能发生的局面x,我们如下定义它的sg值:
(1)若当前局面x为终结局面,则sg值为0。
(2)若当前局面x非终结局面,其sg值为:sg(x) = mex{sg(y) | y是x的后继局面}。
mex{a[i]}表示a中未出现的最小非负整数。举个例子来说:
mex{0, 1, 2} = 3, mex{1, 2}=0, mex{0,1,3}=2
可以发现,若一个局面x为P局面,则有sg(x)=0;否则sg(x)>0。同样sg值也满足N、P之间的转换关系:
若一个局面x,其sg(x)>0,则一定存在一个后续局面y,sg(y)=0。
若一个局面x,其sg(x)=0,则x的所有后续局面y,sg(y)>0。

摘自https://blog.csdn.net/qq_36614777/article/details/80517344

学过博弈论的都知道,当多个博弈同时进行(比如尼姆博弈)时,我们通过将其各个博弈状态的sg值求个异或和以确定其输赢情况,其中我们发现:
1.当异或和为0的时候,我们怎么转移,异或和都不为0
2.当异或和不为0的时候,我们一定至少一种转移方法可以使得异或和为0

 

也是基于这两个结论,我们才能通过求异或和的方式确定输赢。

那么如何证明这两个结论呢?

证明1:
已知n个数异或和为0
证明无论如何转移,异或和都不为0

反证法
假设我们可以转移到异或和为0的状态
设我们所改变的其中一个状态的sg值为k,改变之后的状态的sg值为k′,其余所有的状态的sg值异或和为t
由于当前异或和为0可知
k^t=0
由于状态改变之后k变成了k′,我们可以转化为原来的异或和先异或一个k再异或一个k′,且异或和仍然为0
即k^t^k^k′=0
即k^k′=0
即k=k′
显然不符合sg

函数的性质
所以假设不成立
所以异或和为0的状态一定只能转移成异或和不为0的状态。

证明2:
已知当前异或和不为0
证明一定有一种转移可以使异或和为0

设当前异或和为k
设k的二进制中第a1,a2……an位为1(其中a1>a2>……>an)
当前的所有博弈状态一定可以找到一个状态的sg值的第a1位为1
设这个状态为J,J的sg值为R
将R的a1,a2……an位取反,得到一个数P
因为R的第a1位为1,P的第a1位为0
所以P<R
根据sg函数的性质,sg值为P的状态J一定可以转移到sg值为P比他小的状态
而当异或和中的一项R变为P之后,异或和变为0
所以异或和不为0的状态一定可以转移到异或和为0的状态

证毕

摘自https://blog.csdn.net/hndu__lz/article/details/61925818

对于n个棋子,设它们对应的顶点的SG值分别为(a1,a2,…,an),再设局面(a1,a2,…,an)时的Nim游戏的一种必胜策略是把ai 变成k,那么原游戏的一种必胜策略就是把第i枚棋子移动到一个SG值为k的顶点。对于某个局面(a1,a2,...,an),若a1^a2^...^an<>0,一定存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。不妨设a1^a2^...^an=k,则一定存在某个ai,它的二进制表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。这时ai^k<ai一定成立。则我们可以将ai改变成ai'=ai^k,此时a1^a2^...^ai'^...^an=a1^a2^...^an^k=0。

摘自https://blog.csdn.net/PhilipsWeng/article/details/48395375

摘自https://blog.csdn.net/lgdblue/article/details/15809893

    1、定义:

    (1)两人参与。

    (2)游戏局面的状态集合是有限。

    (3)对于同一个局面,两个游戏者的可操作集合完全相同

    (4)游戏者轮流进行游戏。

    (5)当无法进行操作时游戏结束,此时不能进行操作的一方算输。

    (6)无论游戏如何进行,总可以在有限步数之内结束。

    2、模型:给定一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移动者判负。事实上,这个游戏可以认为是所有公平组合游戏(Impartial Combinatori Games)的抽象模型。其实,任何一个ICG都可以通过把每个局势看成一个顶点,对每个局势和它的子局势连一条有向边来抽象成这个“有向图游戏”。

    3、解决思路:

    现在,假定我们给出两个游戏G1 和 G2。如果我们只知道单个游戏的P-状态和N-状态我们能够正确地玩好游戏和G1 + G2吗?答案是否定的。不难看出两个P-状态的和总是P-状态,P-状态和N-状态的和总是N-状态。但是两个N-状态的和既可能是P-状态也可能是N-状态。因此,只知道单个游戏的P-状态和N-状态是不够的。

    为了正确地玩好游戏和我们需要推广P-状态和N-状态,它就是Sprague-Grudy函数(或者简称为g函数)

    4、Sprague-Grudy定理:

    令N = {0, 1, 2, 3, ...} 为自然数的集合。Sprague-Grundy 函数给游戏中的每个状态分配了一个自然数。结点v的Grundy值等于没有在v的后继的Grundy值中出现的最小自然数.

    形式上:给定一个有限子集 S ⊂ N,令mex S(最小排斥值)为没有出现在S中的最小自然数。定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。

    对于一个给定的有向无环图,定义关于图的每个顶点的Sprague-Garundy函数g如下:g(x)=mex{ g(y) | y是x的后继 }。

    5、性质:

    (1)所有的终结点所对应的顶点,其SG值为0,因为它的后继集合是空集——所有终结点是必败点(P点)。

    (2)对于一个g(x)=0的顶点x,它的所有后继y都满足g(y)!=0——无论如何操作,从必败点(P点)都只能进入必胜点(N点)//对手走完又只能把N留给我们。

    (3)对于一个g(x)!=0的顶点,必定存在一个后继点y满足g(y)=0——从任何必胜点(N点)操作,至少有一种方法可以进入必败点(P点)//就是那种我们要走的方法。

    6、应用:

    (1)可选步数为1-m的连续整数,直接取模即可,SG(x) = x % (m+1);

    (2)可选步数为任意步,SG(x) = x;

    (3)可选步数为一系列不连续的数,用mex(计算每个节点的值)

    7、练习:hdoj 1847 1536 3980

题目

HDU-1847 //Good Luck in CET-4 Everybody! HDU - 1847   //在线测评地址https://vjudge.net/problem/HDU-1847

HDU-1848 //Fibonacci again and again HDU - 1848   //在线测评地址https://vjudge.net/problem/HDU-1848

题解

HDU-1847 //Good Luck in CET-4 Everybody! HDU - 1847   //在线测评地址https://vjudge.net/problem/HDU-1847

//Good Luck in CET-4 Everybody! HDU - 1847
//在线测评地址https://vjudge.net/problem/HDU-1847
//样例通过,提交Wrong Answer.2019-7-30 15:22
//仔细想想,确实有问题,8可分成8或者4,4或者4,2,2或者2,2,2,2等等
//问题想得简单了,还是用SG函数
//样例通过,提交AC.还不错,第一次用SG函数.2019-7-30 23:23
#include <stdio.h>
#include <string.h>
int SG[1010],S[1010],f[15];
int getSG(int n){
    int i,j;
    f[0]=1,SG[0]=0;
    for(i=1;i<=9;i++)f[i]=f[i-1]*2;
    for(i=1;i<=n;i++){
        memset(S,0,sizeof(S));
        for(j=0;f[j]<=i&&j<=9;j++) S[SG[i-f[j]]]=1;//此处写成for(j=1;f[j]<=i&&j<=9;j++) S[SG[i-f[j]]]=1;
        for(j=0;j<=i;j++)
            if(!S[j]){
                SG[i]=j;
                break;
            }
    }
}
int main(){
    int n;
    getSG(1000);
    while(scanf("%d",&n)!=EOF){
        if(!SG[n])printf("Cici\n");
        else printf("Kiki\n");
    }
    return 0;
}

HDU-1848 //Fibonacci again and again HDU - 1848   //在线测评地址https://vjudge.net/problem/HDU-1848

//Fibonacci again and again HDU - 1848
//在线测评地址https://vjudge.net/problem/HDU-1848
//样例通过,提交AC.2019-8-1 22:30
#include <stdio.h>
#include <string.h>
int f[20],SG[1010],S[1010];
void init(){
    int i;
    f[1]=1,f[2]=2;
    for(i=3;f[i-1]<=1000;i++)f[i]=f[i-1]+f[i-2];
    f[0]=i-2;//用来统计f[i]的边界
}
void getSG(){
    int i,j;
    SG[0]=0;
    for(i=1;i<=1000;i++){
        memset(S,0,sizeof(S));
        for(j=1;j<=f[0]&&f[j]<=i;j++) S[SG[i-f[j]]]=1;
        for(j=0;j<=i;j++)//此处写成for(j=0;j<=1000;j++)
            if(!S[j]){
                SG[i]=j;
                break;
            }
    }
}
int main(){
    int m,n,p,ans;
    init();
    getSG();
    while(scanf("%d%d%d",&m,&n,&p)){
        if(m==0&&n==0&&p==0)break;
        ans=SG[m]^SG[n]^SG[p];
        if(ans)printf("Fibo\n");
        else printf("Nacci\n");
    }
    return 0;
}

 

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值