zoukankan      html  css  js  c++  java
  • 组合博弈游戏

     

    ² 组合博弈游戏的概念和特点

     

    ² 组合博弈游戏应满足以下性质:

     

    ² 1. 有两个游戏者。

     

    ² 2. 有一个可能的游戏状态集。这个状态集通常是有限的。

     

    ² 3. 游戏规则指定了在任何状态下双方的可能的走步和对应的后继状态集。如果在任意状态下双方的走步集合是相同的,那么说游戏是公平的(impartial) ,否则是不公平的(partizan) 。象棋是不公平的,因为每个人只能移动自己的子。

     

    ² 4. 两个游戏者轮流走步。

     

    ² 5. 当到达一个没有后继状态的状态后,游戏结束。在普通游戏规则(normal playrule) 下,最后一个走步的游戏者胜;在misµere游戏规则下,最后一个走步的游戏者输。如果游戏无限进行下去,我们认为双方打平,但通常我们会附加规定:

     

    ² 6. 不管双方怎么走步,游戏总能在有限步后结束。

     

    ² 其他规则包括:不允许随机走步(不能扔色子或者随机洗牌),且必须信息完全的(如隐藏走步是不允许的),有限步结束时不能产生平局。在本节中,我们只考虑公平游戏,并且通常只考虑普通游戏规则(最后走步的胜)。

     

    ² 和一般的双人零和博弈不同的是,这里的博弈游戏是特殊的:它们很好的数学特性,使得我们能够找到可判定输赢的数学策略,而不需要进行状态空间的搜索。

     

    ² P状态和N状态:假设双方都采取最明智的策略,则对于一些状态,刚完成走步的游戏者(Previous Player) 一定胜利,而对于其他状态,下一个走步的游戏者(NextPlayer) 一定胜利。把两种状态称为P状态(P position) 和N状态(N position) ,且有以下关系:

     

    ² 1. 所有终止状态是P状态

     

    ² 2. 能一步到达P状态的状态为N状态

     

    ² 3. 每一步都将到达N状态的状态为P状态

     

    ² 我们也可以把P状态称为必败态,N状态称为必胜态,含义是直观的。以上关系实际上给出了一个递推计算所有状态的P-N标号的算法。只要状态集构成一个n个结点m条边的有向无环图(directed acyclic graph, DAG) ,则可以按照拓扑顺序在O(m)时间内计算所有状态的标号。可问题在于这样的状态往往有很多,能否通过数学方法直接判断一个状态是P状态还是N状态呢?

     

    ² 常见的组合博弈模型,有若干种,但也有很多情况,不能套用这些模型,要具体情况具体分析。

     

    ² 博弈树模型

     

    ² 假设甲乙双方在进行这种二人游戏,从唯一的一个初始局面开始,如果轮到甲方走棋,甲方有很多种着法,但只能选择一个着法进行走棋。甲方走棋后,局面发生了变化,轮到乙方走棋,乙方也有很多种着法,但也只能选择一个着法。从初始局面开始,甲乙两方交替走棋,局面的变化可以表示成一个树形结构,这就是博弈树(game-tree)。一种井字棋的博弈树,如图所示。

     

    ² 每个局面可以用博弈树的一个结点来表示,某方获胜、失败或双方平局的结点构成了叶子结点。甲乙双方在选择着法时,不仅要考虑己方每一种着法的好坏,同时也要考虑对方会针对自己的每一种着法采取怎样的着法来应对。显然,博弈树是一种特殊的与或树,“或”结点和“与”结点是逐层交替出现的。己方扩展的结点之间是“或”关系,对方扩展的结点之间是“与”关系。

     

    ² Bash’s Game(巴什博弈)

     

    ² 所谓巴什博弈,是ACM题中最简单的组合游戏,大致上是这样的:

     

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

     

    ² 显然,如果n = m + 1,那么由于一次最多只能取m个,所以,无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,后者取胜。因此我们发现了如何取胜的法则:

     

    ² 如果 n = (m + 1) * r + s ,(r为任意自然数,s≤m),即n%(m+1) != 0,则先取者肯定获胜。

     

    ² 巴什博弈还是很好理解的,以你是先手的角度考虑。你想把对手给弄垮,那么每一局,你都必须构建一个局势,这个局势就是每次都留给对手m+1的倍数个物品。因为,如果n=(m+1)r + s,(r为任意自然数,s≤m),那么先取者要拿走s个物品,如果后取者拿走k(≤m)个,那么先取者再拿走m+1-k个,结果剩下(m+1)(r-1)个,以后保持这样的取法,那么先取者肯定获胜。总之,要保持给对手留下(m+1)的倍数,就能最后获胜。

     

    ² 这个游戏还可以有一种变相的玩法:两个人轮流报数,每次至少报1个,最多报10个,谁能报到100者胜。

     

    ² 好运!该死的英语四级!

     

    ² Problem Description
    大学英语四级考试就要来临了, Kiki和Cici 在紧张的复习之余喜欢打牌放松。“升级”?“斗地主”?那多俗啊!作为计算机学院的学生,Kiki和Cici打牌的时候可没忘记专业,她们打牌的规则是这样的:
    1、 总共n张牌;
    2、 双方轮流抓牌;
    3、 每人每次抓牌的个数只能是2的幂次(即:1,2,4,8,16…)
    4、 抓完牌,胜负结果也出来了:最后抓完牌的人为胜者;
    假设Kiki和Cici都是足够聪明并且每次都是Kiki先抓牌,请问谁能赢呢?

     

    ² Input
    输入数据包含多个测试用例,每个测试用例占一行,包含一个整数n(1<=n<=1000)。

     

    Output
    若Kiki能赢的话输出“Kiki”,否则输出“Cici”,每个实例的输出占一行。

     

    Sample Input
    1
    3

     

    Sample Output
    Kiki
    Cici

     

    ² 如果你是先手,考虑你的必胜态。注意,因为任何正整数都能写成若干个2的整数次方幂之和。由于规定只能取2的某个整数次方幂,只要你留给对手的牌数为3的倍数时,那么你就必赢,因为留下3的倍数时,对手有两种情况:

     

    ² 1:如果轮到对方抓牌时只剩3张牌,对方要么取1张,要么取2张,剩下的你全取走,win!

     

    ² 2:如果轮到对方抓牌时还剩3*k张牌,对手不管取多少,剩下的牌数是3*x+1或者3*x+2。轮到你时,你又可以构造一个3的倍数。 所以无论哪种情况,当你留给对手为3*k的时候,你是必胜的。

     

    ² 题目说Kiki先抓牌,那么当牌数为3的倍数时,Kiki就输了。否则Kiki就能利用先手优势将留给对方的牌数变成3的倍数,就必胜。

     

    ² #include <iostream>
    using namespace std;
    int main ()
    {
    int N;
    while ( cin >> N )
    {
    puts ( N % 3 != 0 ? “Kiki” : “Cici” );
    }
    return 0;
    }

     

    ² 土地拍卖

     

    ² Problem Description

     

    ² 小鸡同学和鹏程同学始终没有逃过退学的命运,因为他们没有在程序设计竞赛中获奖,还为了争抢莎莎大打出手。现在等待他们的只能回家种田。要种田得有田才行,小鸡听说街上正在举行一场拍卖会,拍卖的物品正好就是一块田地。于是,小鸡带上他的全部积蓄,冲往拍卖会。后来发现,整个拍卖会只有小鸡和他的死对头鹏程。通过打听,小鸡知道这场拍卖的规则是这样的:刚开始底价为0,两个人轮流开始加价,不过每次加价的幅度要在1~N之间,当价格大于或等于田地的成本价M时,就把这块田地卖给这次叫价的人。小鸡和鹏程虽然比赛不行,但是对拍卖却十分精通,而且他们两个人都十分想得到这块田地。所以他们每次都是选对自己最有利的方式进行加价。由于抽签决定,所以每次都是由小鸡先开始加价,请问,第一次加价的时候,小鸡要出多少才能保证自己买得到这块地呢?

     

    ² Input

     

    ² 本题目包含多组测试,请处理到文件结束(EOF)。每组测试占一行。每组测试包含两个整数M和N(含义见题目描述,0<N,M<1100)

     

    ²

     

    ² Output

     

    ² 对于每组数据,在一行里按递增的顺序输出小鸡第一次可以加的价。两个数据之间用空格隔开。如果小鸡在第一次无论如何出价都无法买到这块土地,就输出”none”。

     

    ²

     

    ² Sample Input

     

    ² 4 2

     

    ² 3 2

     

    ² 3 5

     

    ²

     

    ² Sample Output

     

    ² 1

     

    ² None

     

    ² 3 4 5

     

    ² int main()
    {
    int n,m;
    while(scanf(“%d%d”,&m,&n)!=EOF)
    {
    if(m%(n+1)==0)
    {
    printf(“none\n”);
    continue;
    }

     

    ²         if(m<=n)
    {
    printf(“%d”,m);
    for(int i=m+1; i<=n; i++) printf(” %d”,i);
    printf(“\n”);
    continue;
    }
    printf(“%d\n”,m%(n+1));
    }
    return 0;
    }

     

    ² 减法博弈

     

    ² 巴什博弈有一种变形,叫做减法博弈。用s表示一个正整数构成的集合,基于S所定义的减法游戏可以描述如下

     

    ² 有一个由n个石子组成的石子堆,两名玩家轮流从中拿走石子,每次拿走石子的个数只能是集合S中的数。拿走最后一枚石子的玩家获胜。

     

    ² 例:S = {1, 3, 4},如果在游戏的开始有100枚石子,那么哪个玩家获胜?

     

    • 使用向后归纳的方法,可以计算出游戏的P位置和N位置如下:
    • 通过观察发现,该游戏中的P位置(必败态)是那些能被7整除或者模7余2的位置,其他位置都是N位置(必胜态)。用数学归纳法可以证明这个结论是正确的。
    • 事实上,如果k是P态(自己必败),那么P+1、P+3、P+5能够到达的位置,是N态(对方必败),其他位置是P态。
    • 游戏的起始位置100是P位置,所以先手输。

     

    ² Wythoff’s Game (威佐夫博弈)

     

    ² 所谓威佐夫博弈,是ACM题中常见的组合游戏中的一种,大致上是这样的:

     

    ² 有两堆石子,不妨先认为一堆有 10,另一堆有 15 个,双方轮流取走一些石子,合法的取法有如下两种:

     

    ² 1、在一堆石子中取走任意多颗;

     

    ² 2、在两堆石子中取走相同多的任意颗;

     

    ² 约定取走最后一颗石子的人为赢家,求必胜策略。

     

    ² 两堆石头地位是一样的,我们用余下的石子数(a,b)来表示状态,并画在平面直角坐标系上。

     

    ² 和前面类似,(0,0)肯定是 P 态,又叫必败态。(0,k),(k,0),(k,k)系列的节点肯定不是 P 态,而是必胜态,你面对这样的局面一定会胜,只要按照规则取一次就可以了。再看 y = x 上方未被划去的格点,(1,2)是 P 态。k > 2 时,(1,k)不是 P 态,比如你要是面对(1,3)的局面,你是有可能赢的。同理,(k,2),(1 + k, 2 + k)也不是 P 态,划去这些点以及它们的对称点,然后再找出 y = x 上方剩余的点,你会发现(3,5)是一个 P 态,如此下去,如果我们只找出 a ≤ b 的 P 态,则它们是(0,0),(1,2),(3,5),(4,7),(6,10)……它们有什么规律吗?

     

    ² 忽略(0,0),很快会发现对于第 i 个 P 态的 a,a = i * (sqrt(5) + 1)/2 然后取整;而 b = a + i。居然和黄金分割点扯上了关系。

     

    ² 前几个必败点如下:(0,0),(1,2),(3,5),(4,7),(6,10),(8,13)……可以发现,对于第k个必败点(m(k),n(k))来说,m(k)是前面没有出现过的最小自然数,n(k)=m(k)+k。

     

    ² 判断一个点是不是必败点的公式与黄金分割有关(我无法给出严格的数学证明,谁能给出严格的数学证明记得告诉我),为:

     

    ² m(k) = k * (1 + sqrt(5))/2

     

    ² n(k) = m(k) + k

     

    ² 一个必败点有如下性质:

     

    ² 性质1:所有自然数都会出现在一个必败点中,且仅会出现在一个必败点中;

     

    ² 性质2:规则允许的任意操作可将必败点移动到必胜点;

     

    ² 性质3:一定存在规则允许的某种操作可将必胜点移动到必败点;

     

    ² 下面我们证明这3个性质。

     

    ² 性质1:所有自然数都会出现在一个必败点中,且仅会出现在一个必败点中;

     

    ² 证明:m(k)是前面没有出现过的最小自然数,自然与前k-1个必败点中的数字都不同;m(k)>m(k-1),否则违背m(k-1)的选择原则;n(k)=m(k)+k>m(k-1)+(k-1)=n(k-1)>m(k-1),因此n(k)比以往出现的任何数都大,即也没有出现过。又由于m(k)的选择原则,所有自然数都会出现在某个必败点中。性质1证毕。

     

    ² 性质2:规则允许的任意操作可将必败点移动到必胜点;

     

    ² 证明:以必败点(m(k),n(k))为例。若只改变两个数中的一个,由于性质1,则得到的点一定是必胜点;若同时增加两个数,由于不能改变两数之差,又有n(k)-m(k)=k,故得到的点也一定是必胜点。性质2证毕。

     

    ² 性质3:一定存在规则允许的某种操作可将必胜点移动到必败点;

     

    ² 证明:以某个必胜点(i,j)为例,其中j>i。因为所有自然数都会出现在某个必败点中,故要么i等于m(k),要么j等于n(k)。

     

    ² 若i=m(k),j>n(k),可从j中取走j-n(k)个石子到达必败点;

     

    ² 若i=m(k),j<n(k),可从两堆同时拿走m(k)-m(j-m(k)),注意此时j-m(k) < n(k)-m(k) < k,从而到达必败点( m(j-m(k)),m(j-m(k))+j-m(k));

     

    ² 若i>m(k),j=n(k),可从i中取走i-m(k)个石子到达必败点;

     

    ² 若i<m(k),j=n(k),需要再分两种情况,因为i一定也出现在某个必败点中,若i=m(l),则从j中拿走j-n(l),若i=n(l),则从j中拿走j-m(l),从而到达必败点(m(l),n(l))。

     

    ² 性质3证毕。

     

    ² 移动的皇后

     

    ² Problem Description

     

    ² 一个n * n棋盘上有一个皇后。每个人可以把它往左或下或左下45度移动任意多步。把皇后移动至左下角的游戏者获胜。现在给出皇后初始的X坐标和Y坐标,如果轮到你先走,假设双方都采取最好的策略,问最后你是胜者还是败者。

     

    ² Input
    输入包含若干行,表示若干种皇后的初始情况,其中每一行包含两个非负整数a和b,表示皇后的初始坐标,a和b都不大于1,000,000,000。

     

    ² Output
    输出对应也有若干行,每行包含一个数字1或0,如果最后你是胜者,则为1,反之,则为0。

     

    ² Sample Input

     

    ² 2 1
    8 4
    4 7

     

    ² Sample Output

     

    ² 0
    1
    0

     

    ² #include <iostream>

     

    ² using namespace std;

     

    ² int main()

     

    ² {

     

    ²     int m,n;

     

    ²     while(cin>>m>>n)

     

    ²     {
    if (m > n) { int temp; temp = m; m = n; n =temp; }

     

    ²          int k = n – m;

     

    ²          int data = floor(k*(1.0+sqrt(5.0))/2.0);

     

    ²          if (data == m) cout<<0<<endl;

     

    ²     }

     

    ² }

     

    ² Ferguson博弈(清空/分割游戏)

     

    ² 清空/分割游戏也叫做Ferguson博弈。进行游戏需要用到两个盒子。在游戏的开始,第一个盒子中有n枚石子,第二个盒子中有m个石子(n, m > 0)。参与游戏的两名玩家轮流执行这样的操作:清空一个盒子中的石子,然后从另一个盒子中拿若干石子到被清空的盒子中,使得最后两个盒子都不空。当两个盒子中都只有一枚石子时,游戏结束。最后成功执行操作的玩家获胜。找出游戏中所有的P位置。

     

    ² 对于一个位置(x, y)来说,如果x, y中有一个偶数,那么(x, y)是N(必胜)位置。如果x和y都是奇数,那么(x, y)是P位置(必败)。可以用数学归纳法证明。

     

    ² 证明(引自数学系唐思斯老师的证明):

     

    ² 证明结论:(x,y)至少一偶时,先手胜;都为奇时,先手败

     

    ² 证明:

     

    ² (x,y)=(1,1)时是先手必败态

     

    ² 下对max(x,y)>1进行归纳

     

    ² 1、当max(x,y)=2时,即(x,y)=(1,2)或(2,1)或(2,2),先手留下一个2分为(1,1),先手获胜

     

    ² 即当max(x,y)=2时结论成立。

     

    ² 2、假设max(x,y)<k时结论都成立,现证max(x,y)=k时结论成立

     

    ² 若(x,y)中有一个偶数(设为a),先手将另一个清空,把偶数a分为两个奇数b和c,由于b、c<a 小于等于 k,即max(b,c)<k,由假设,在(b,c)位置上后手作为新先手必败,故先手胜

     

    ² 若(x,y)都为奇数,先手只能保留一个奇数并将其分解为一奇a一偶b,由于max(a,b)<max(x,y)=k,由假设,在(a,b)位置上后手作为新先手必胜,故先手败

     

    ² Chomp! 博弈(巧克力游戏)

     

    ² 情人节的时候,潘典安买了一块长方形的棋盘巧克力,和他最爱的女友一起吃,巧克力由n*m块格子组成。为了增加情人节的情趣,潘典安和她女友轮流选择一个格子,并把这个格子右面,上面和右上方的巧克力全部取走。取到左下角格子的玩家输,就要给对方一个热烈的吻。假设每次都是潘典安先手,他是否存在必胜策略呢?

     

    ² 下图可以看作是一个3*8的巧克力被拿掉(6,2)和(3,2)两块后剩下的形状:

     

    ² 答案是除了1*1的棋盘,对于其他大小的棋盘,先手总能赢。有一个很巧妙的证明可以保证先手存在必胜策略,可惜这个证明不是构造性的,也就是说没有给出先手怎么下才能赢。证明如下:

     

    ² 如果后手能赢,也就是说后手有必胜策略,使得无论先手第一次取哪个石子,后手都能获得最后的胜利。那么现在假设先手取最右上角的石子(n,m),接下来后手通过某种取法使得自己进入必胜的局面。但事实上,先手在第一次取的时候就可以和后手这次取的一样,进入必胜局面了,与假设矛盾。

     

    ² Fibonacci’s Game (斐波那契博弈)

     

    ² 斐波那契博弈模型,是ACM题中常见的组合游戏中的一种,大致上是这样的:

     

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

     

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

     

    ² 2. 之后每次可以取的石子数介于 1 到对手刚取的石子数的 2 倍之间(包含 1 和对手刚取的石子数的 2 倍)。

     

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

     

    ² 这个和之前的威佐夫博弈和取石子游戏有一个很大的不同点,就是游戏规则的动态化。之前的规则中,每次可以取的石子的策略集合是基本固定的,但是这次有规则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颗中的最后一颗,那么先手赢。

     

    ² 尼姆博弈(Nimm’s Game)

     

    ² 尼姆博弈模型,是ACM题中常见的组合游戏中的一种,大致上是这样的:

     

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

     

    ² 这种情况最有意思,它与二进制有密切关系,我们用(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)的情形。

     

    ² 计算机算法里面有一种叫做按位模2加,叫做异或的运算,我们用符号XOR表示这种运算,这种运算和一般加法不同的一点是1 XOR 1 = 0。先看(1,2,3)的按位模2加的结果:

     

    ² 1 = 二进制01

     

    ² 2 = 二进制10

     

    ² 3 = 二进制11  XOR

     

    ² ———————

     

    ² 0 = 二进制00 (注意不进位)

     

    ²

     

    ² 对于奇异局势(0,n,n)也一样,结果也是0。

     

    ² 任何奇异局势(a,b,c)都有a XOR b XOR c = 0。

     

    ² 如果我们面对的是一个非必败态(a,b,c),要如何变为必败态呢?假设 a < b < c,我们只要将 c 变为a XOR b,即可。因为有如下的运算结果:

     

    ² a XOR b XOR (a XOR b)=(a XOR a) XOR (b XOR b) = 0 XOR 0 = 0。

     

    ² 要将c 变为a XOR b,只要对 c进行 c-(a XOR b)这样的运算即可。

     

    ² 尼姆博弈模型可以推广到:有n堆若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

     

    ² 这个游戏中的变量是堆数k和各堆的物品数N1,N2,……,Nk。对应的组合问题是,确定先手获胜还是后手获胜以及两个游戏人应该如何取物品才能保证自己获胜(获胜策略)。

     

    ² 为了进一步理解Nim取物品游戏,我们考查某些特殊情况。如果游戏开始时只有一堆物品,先手则通过取走所有的物品而获胜。现在设有2堆物品,且物品数量分别为N1和N2。游戏者取得胜利并不在于N1和N2的值具体是多少,而是取决于它们是否相等。设N1!=N2,先手从大堆中取走的物品使得两堆物品数量相等,后手再拿,于是,先手以后每次取子的数量与后手相等而最终获胜。但是如果N1= N2,则:后手只要按着先手拿的数量在另一堆中取相等数量的物品,最终获胜者将会是后手。这样,两堆的取子获胜策略就已经找到了。

     

    ² 现在我们如何从两堆的取子策略扩展到任意堆数中呢?

     

    ² 首先来回忆一下,每个正整数都有对应的一个二进制数,例如:57(10) = 111001(2) ,即:57(10)=25+24+23+20。于是,我们可以认为每一堆物品数由2的幂数的子堆组成。这样,含有57枚物品大堆就能看成是分别由数量为25、24、23、20的各个子堆组成。

     

    ² 现在考虑各大堆大小分别为N1,N2,……Nk的一般的Nim博弈。将每一个数Ni表示为其二进制数(数的位数相等,不等时在前面补0):

     

    ² N= as…a1a0

     

    ² N= bs…b1b0

     

    ² ……

     

    ² N= ms…m1m0

     

    ² 如果每一种大小的子堆的个数都是偶数,我们就称Nim博弈是平衡的,而对应位相加是偶数的称为平衡位,否则称为非平衡位。因此,Nim博弈是平衡的,当且仅当:

     

    ² a+bs + … + ms 是偶数,即aXOR bs XOR … XOR ms  = 0

     

    ² ……

     

    ² a+b+ … + m是偶数,即aXOR b1 XOR … XOR m1 = 0

     

    ² a+b0 + … + m0是偶数,即aXOR b0 XOR … XOR m0 = 0

     

    ² 于是,我们就能得出尼姆博弈中先手获胜策略:

     

    ² Bouton定理:先手能够在非平衡尼姆博弈中取胜,而后手能够在平衡的尼姆博弈中取胜。即,状态(x1, x2, x3, …, xn)为P状态当且仅当x1 xor x2 xor x3 xor … xor xn=0。这样的操作也称为Nim和(Nim Sum) 。

     

    ² 我们以一个两堆物品的尼姆博弈作为试验。设游戏开始时游戏处于非平衡状态。这样,先手就能通过一种取子方式使得他取子后留给后手的是一个平衡状态下的游戏,接着无论后手如何取子,再留给先手的一定是一个非平衡状态游戏,如此反复进行,当后手在最后一次平衡状态下取子后,先手便能一次性取走所有的物品而获胜。而如果游戏开始时游戏牌平衡状态,那根据上述方式取子,最终后手能获胜。

     

    ² 下面应用此获胜策略来考虑4堆的Nim博弈。其中各堆的大小分别为7,9,12,15枚硬币。用二进制表示各数分别为:0111,1001,1100和1111。于是可得到如下一表:

     

    ²

     

    ² 由Nim博弈的平衡条件可知,此游戏是一个非平衡状态的Nim博弈,因此,先手在按获胜策略一定能够取得最终的胜利。具体做法有多种,先手可以从大小为12的堆中取走11枚硬币,使得游戏达到平衡(如下表),

     

    ² 之后,无论后手如何取子,先手在取子后仍使得游戏达到平衡。

     

    ² 同样的道理,先手也可以选择大小为9的堆并取走5枚硬币而剩下4枚,或者,先手从大小为15的堆中取走13枚而留下2枚。

     

    ² 归根结底, Nim博弈的关键在于游戏开始时游戏处于何种状态(平衡或非平衡)和先手是否能够按照取子游戏的获胜策略来进行游戏。

     

    ² 当堆数大于2时,我们看出Bouton定理依旧适用,但还没给出严格的证明,下面用数学归纳法证明。

     

    ² 证明:如果每堆都为0,显然是P状态(必败)。下面验证P状态和N状态的后两个递推关系:

     

    ² 一、每个N状态都可以一步到达P状态。

     

    ² 证明是构造性的。检查Nim和X的二进制表示中最左边一个1,则随便挑一个该位为1的物品堆Y,根据Nim和进行调整(0变1,1变0)即可。例如Nim和为100101011,而其中有一堆为101110001。为了让Nim和变为0,只需要让操作的物品数取操作前的物品数和Nim的异或即可。

     

    ² 显然操作后物品数变小,因此和合法的。设操作前其他堆的Nim和为Z,则有Y xor Z = X。操作后的Nim和为X xor Y xor Z = X xor X = 0,是一个P状态。

     

    ² 二、每个P状态(必胜态)都不可以一步到达P状态

     

    ² 由于只能改变一堆的物品,不管修改它的哪一位,Nim的对应位一定不为0,不可能是P状态。

     

    ² 这样就证明了Bouton定理。

     

    ² Nim博弈中如果规定最后取光者输,情况是怎样的?初看起来问题要复杂很多(因为不能主动拿了,而要“躲着”拿,以免拿到最后一个物品),事实上确实有很多游戏规则比普通规则要困难很多,但对于Nim游戏来说,几乎是一样的:

     

    ² 首先按照普通规则一样的策略进行,直到恰好有一个物品数大于1的堆x。在这样的情况下,只需要把堆x中的物品拿得只剩1个物品或者拿完,让对手面临奇数堆物品,这奇数堆物品每堆恰好1个物品。这样的状态显然是必败的。由于你每次操作后需要保证Nim和为0,因此不可能在你操作后首次出现“恰好有一个物品数大于1的堆”。新游戏得到了完美解决。

     

    ² SG函数与SG定理

     

    ² 现在我们来研究一个看上去似乎更为一般的游戏:给定一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移动者判负。事实上,这个游戏可以认为是所有Impartial Combinatorial Games的抽象模型。也就是说,任何一个ICG都可以通过把每个局面看成一个顶点,对每个局面和它的子局面连一条有向边来抽象成这个“有向图游戏”。下面我们就在有向无环图的顶点上定义Sprague-Garundy函数,简称SG函数。

     

    ² 首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如集合N={0,1,2,3,4,5,6},则mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。

     

    ² Sprague-Grundy 函数是定义在组合游戏状态上的函数,用g (X)表示X状态的g函数值。它的定义如下:

     

    ² g(x)=mex{ g(y) | y是x的后继 }。

     

    ² 或者表示为:

     

    ² g (X)= min{n| n∈N, n>=0,n≠ for y, y是x的后继}

     

    ² 形象的说就是X的g函数值为X的后继点的SG值中没有出现过的最小值。

     

    ² SG函数的性质:

     

    ² 1、所有的终点(先手必败态),也就是没有出边的顶点,其SG值为0,因为它的后继集合是空集;

     

    ² 2、对于一个g(x)=0的顶点x,它的所有后继y都满足g(y)!=0;

     

    ² 3、对于一个g(x)!=0的顶点,必定存在一个后继y满足g(y)=0。

     

    ² 下面将引入Sprague-Grundy定理,简称SG定理,并给上述的SG函数的性质进行证明。

     

    ² 通过下面的定理可以建立SG函数与N局面和P局面的关系:

     

    ² 引理:对于任意的局面x,若g(x)=0则x是P局面(必败态),否则x是N局面(必胜态)。

     

    ² 证明:我们对局面作数学归纳法:

     

    ² 1. 对于最终局面x,由定义x是P局面(必败态),而此时g(x)=0, 引理成立。

     

    ² 2. 假设局面x的所有后继局面都满足引理。

     

    ³ 若x的后继局面中存在P局面(必败态)y,则x是N局面(必胜态),同时g(y)=0,故g(x)不等于0;

     

    ³ 若x的后继局面y中不存在P局面,则显然x是P局面;同时由于不存在g(y)=0,故g(x)=0。

     

    ² 由归纳假设,引理对于所有局面x成立。

     

    ² 我们通过计算有向无环图的每个顶点的SG值,就可以对每种局面找到必胜策略了。

     

    ² 实际中,SG函数的用途远没有这样简单。如果将有向图游戏变复杂一点,比如说,有向图上并不是只有一枚棋子,而是有n枚棋子,每次可以任选一颗进行移动,这时,怎样找到必胜策略呢?

     

    ² 再来考虑一下顶点的SG值的意义。当g(x)=k时,表明对于任意一个0 ≤ i<k,都存在x的一个后继y满足g(y)=i。也就是说,当某枚棋子的SG值是k时,我们可以把它变成0、变成1、……、变成k-1,但绝对不能保持k不变。不知道你能不能根据这个联想到Nim游戏,Nim游戏的规则就是:每次选择一堆数量为k的石子,可以把它变成0、变成1、……、变成k-1,但绝对不能保持k不变。这表明,如果将n枚棋子所在的顶点的SG值看作n堆相应数量的石子,那么这个Nim游戏的每个必胜策略都对应于原来这n枚棋子的必胜策略!

     

    ² 也就是说:游戏者可以通过一步走棋把图的当前状态值任意的减小(当然必须保证状态值始终>=0)。

     

    ² 如果游戏者处在一个点x,g(x)=0。那么游戏者无论如何移动,下一个点的SG值都不等于0

     

    ² 对于n个棋子,设它们对应的顶点的SG值分别为(a1,a2,…,an),再设局面(a1,a2,…,an)时的Nim游戏的一种必胜策略是把某个ai变成k,那么原游戏的一种必胜策略就是把第i枚棋子移动到一个SG值为k的顶点。这听上去有点过于神奇——怎么绕了一圈又回到Nim游戏上了。

     

    ² 可以用如下方式定义组合游戏的和:初始局面包含每个子游戏的初始局面,而每次每个游戏者可以任意选一个游戏,进行一次合法走步,而让其他游戏局面保持不变。

     

    ² 3堆火柴的Nim游戏可以作看是3个一堆火柴的Nim游戏之和。一堆Nim游戏是简单的:直接把所有火柴拿掉即可;但3堆火柴却复杂得多。看样子,即使每个游戏都很简单,它们的和也可能很复杂。

     

    ² 虽然看起来很复杂,但Sprague-Grundy定理提供了一个很好的方案解决这一问题:

     

    ² Sprague-Grundy定理:游戏和的SG函数等于各子游戏SG函数的Nim和,即:设gi(x)为Gi的SG函数(1 ≤ i ≤ n),则G = G1+ G2+ … + Gn的SG函数g( x1, x2, …, x) = g1(x1)⊕ g2(x2) ⊕ … ⊕ gn(xn)(其中⊕为异或运算)。

     

    ² 证明:对于G的任意局面x ( x1, x2, …, x},设b = g1(x1)⊕ g2(x2) ⊕ … ⊕ gn(xn) ,对于任意一个非负整数a<b,如果我们能证明下面两个事实:

     

    ² (1)都存在x的一个后继局面y,使得g(y)=a;

     

    ² (2)不存在x的后继局面y,使得g(y)=b;

     

    ² 则b就是满足要求的x的SG函数值,命题也就随之得证。

     

    ² 对于事实1,令d=a⊕b,设k是d的二进制位的最高位,即2k-1 ≤ d < 2k。 由于a<b,故a的第k位为0,b的第k位为1。故g1(x1)、 g2(x2) 、… 、gn(xn)中至少有一个二进制位第k位为1,不妨设为g1(x1),那么有d⊕g1(x1)< g1(x1),根据SG函数g1的定义,必存在x1的一个后继局面y1,使得g1(y1)=d⊕g1(x1)。

     

    ² 故存在{x1,x2,…,xn}的一个后继局面{y1,x2,x3,…,xn},它的SG函数值为:

     

    ² g(y1,x2,x3,…,xn)=g1(y1)⊕g2(x2)⊕…⊕gn(xn)

     

    ²                           = d⊕g1(x1)⊕g2(x2)⊕…⊕gn(xn) = d⊕b

     

    ²                           = a⊕b⊕b = a

     

    ² 至此事实1成立

     

    ² 对于事实2,反设若存在这样的后继局面y,不妨设y={y1,x2,…,xn}(y1是x1的后继局面),   则有:

     

    ² g1(x1)⊕g2(x2)⊕…⊕gn(xn) = g1(y1)⊕g2(x2)⊕…⊕gn(xn)

     

    ² 即:g1(x1)=g1(y1),这与SG函数g1的性质矛盾。因此事实2成立。

     

    ² 由Sprague-Grundy定理可知,对于复杂的公平组合博弈,如果我们可以将它们分解成若干个子游戏的联合,就可以通过求解子游戏的SG函数,方便地求得原游戏的SG函数,从而快速判断胜负情况。

     

    ² 有了上面的公平组合博弈的理论为基础,我们就可以来解决引言中提出的问题了。

     

    ² 保龄球游戏

     

    ² 在一行中有n个木瓶,你和你的朋友轮流用保龄球去打这些木瓶,由于你们都是高手,每一次都可以准确的击倒一个或相邻的两个木瓶,谁击倒最后一个剩余的木瓶谁将获得胜利。如果由你先打,请你分析,你应该采取什么策略来确保赢得胜利?

     

    ² 分析:为了看清楚问题的本质,用另一种方式来描述这个游戏。最开始有一堆石子(n个),每一次你可以进行以下四种操作中的一种:

     

    ² 1. 从中取出一颗石子;

     

    ² 2. 从中取出两颗石子;

     

    ² 3. 从一堆中取出一颗石子,并且将这一堆中余下的石子任意分成两堆(每堆至少一颗);

     

    ² 4. 从一堆中取出两颗石子,并且将这一堆中余下的石子任意分成两堆(每堆至少一颗)。

     

    ² 这四种操作,实际上就依次对应于原来游戏中的以下四种击倒法:

     

    ² 1. 击倒一段连续的木瓶中最靠边的一个;

     

    ² 2. 击倒一段连续的木瓶中最靠边的连续两个;

     

    ² 3. 击倒一段连续的木瓶中不靠边的一个;

     

    ² 4. 击倒一段连续的木瓶中不靠边的连续两个。

     

    ² 把局面看作顶点,游戏规则看作边,这是一个典型的图游戏。

     

    ² 如果当前局面被分成了M堆,(A1,A2,…,Am),则SG(A1,A2,…,Am) = SG(A1)⊕SG(A2)⊕…⊕SG(Am)。对某一堆来说:

     

    ² 显然,SG(0)=0。

     

    ² 剩余1个时,只能取到0个,而SG(0)=0,所以SG(1)=1。

     

    ² 剩余2个时,可以取到0或1,其中SG(0)=0,SG(1)=1,所以SG(2)=2。

     

    ² 剩余3个时,我们可以把局面变成1或2或两堆均为1。

     

    ² 其中SG(1)=1,SG(2)=2,SG(1, 1)=SG(1) ⊕ SG(1)=0,所以SG(3)=3。

     

    ² SG(4)可分解为{SG(2), SG(3),SG(2)⊕SG(1), SG(1)⊕SG(1)} ={2,3,3,0},所以SG(4)=1。

     

    ² 对于任意一种局面P,的SG值为SG(P),为了赢得胜利,我们只需将他的局面变成Q,使得SG(Q)=0即可。

     

    ² 对于每一个N值,我们为了求出他的SG值,时间复杂度为O(N) ,如果只要求你求出你第一步应该如何行动,那么这种普通的方法需要O(N2)的复杂度,显然不能令我们满意。

     

    ² 这个问题看似已经解决,但我们可以进行优化。事实上,我们通过观察较小的数的SG值,可以发现:

     

    ²  0 ~11的SG值为:0 1 2 3 1 4 3 2 1 4 2 6

     

    ² 12~23的SG值为:4 1 2 7 1 4 3 2 1 4 6 7

     

    ² 24~35的SG值为:4 1 2 8 5 4 7 2 1 8 6 7

     

    ² 36~47的SG值为:4 1 2 3 1 4 7 2 1 8 2 7

     

    ² 48~59的SG值为:4 1 2 8 1 4 7 2 1 4 2 7

     

    ² 60~71的SG值为:4 1 2 8 1 4 7 2 1 8 6 7

     

    ² 72~83的SG值为:4 1 2 8 1 4 7 2 1 8 2 7

     

    ² 并且从72开始,SG值以12为循环节,不断的重复出现,这样我们求出所有SG值的复杂度就降到了常数,这样判断第一步的如何选择的复杂度就降为了O(N)。

     

    ² 具体问题具体分析

     

    ² 也有很多题目不能完全照搬上述模型的,这就需要具体问题具体分析。来看2011年湖南省程序设计竞赛的题目E:

     

    ² 盒子游戏

     

    ² 题目大意:

     

    ² 有两个相同的盒子,其中一个装了n个球,另一个装了一个球。Alice和Bob发明了一个游戏,规则如下:Alice和Bob轮流操作,Alice先操作。每次操作时,游戏者先看看哪个盒子里的球的数目比较少,然后清空这个盒子(盒子里的球直接扔掉),然后把另一个盒子里的球拿一些到这个盒子中,使得两个盒子都至少有一个球。如果一个游戏者无法进行操作,他(她)就输了。

     

    ² 面对两个各装一个球的盒子,Bob无法继续操作,因此Alice获胜。你的任务是找出谁会获胜。假定两人都很聪明,总是采取最优策略。

     

    ² 输入

     

    ² 输入最多包含300组测试数据。每组数据仅一行,包含一个整数n(2 ≤ n ≤ 109)。输入结束标志为n=0

     

    ² 输出

     

    ² 对于每组数据,输出胜者的名字。

     

    ² 样例输入

     

    ² 2

     

    ² 3

     

    ² 4

     

    ² 0

     

    ² 样例输出

     

    ² Alice

     

    ² Bob

     

    ² Alice

     

    ² 分析:

     

    ² 这个题目,无法套用巴什博弈、威佐夫博弈、斐波那契博弈、尼姆博弈这四个常见模型,和清空/分割游戏也有差别。如果用万能的alpha-beta剪枝算法绝对超时。怎么办?不妨耐心的仔细分析,找出规律,因为竞赛题都是应试教育下的变态产物(不过也能培养一顶的能力),你要做出这类题,第一是要有耐心,第二是你的思维要比出题的刘汝佳之流更加变态才行。

     

    ² 仔细分析,可以发现,既然球数量少的盒子里的球都是要丢弃的,这道题可以转换为,在一个剩下n个球的盒子里,拿走k个球,其中1 ≤ k ≤ int(n/2),还剩下int(n – k)个球。如果某个游戏者面对盒子里的球只剩下1个,即n=1时,就败了。引入函数win(n),表示这个盒子剩下的球数为n时,当前游戏者是否能获得必胜态,由于不存在平局,当win(n)=1时,当前游戏者能获得必胜态,当win(n)=0时由于对手足够聪明,当前游戏者只能获得必败态。所以:

     

    ² n=1时,根据游戏规则,对当前游戏者来说是必败态,即win(1) = 0。

     

    ² n>1时,对当前游戏者拿走k个球,还剩下r个球留给对方,r = n – k,其中1 ≤ k ≤ (int)(n/2),这样,r就有个范围,即int((n + 1)/2) ≤ r ≤ n – 1。博弈树是一种特殊的与或树,“或”结点和“与”结点是逐层交替出现的。己方扩展的结点之间是“或”关系,对方扩展的结点之间是“与”关系(关于博弈树的这段话关紧要,看不懂的同学可以掠过,看得懂的同学能在瞬间明白是怎么回事)。所以只要win(int((n + 1)/2))、win(int((n + 1)/2) + 1)、win(int((n + 1)/2) + 2)、…、win(n – 1)中所有的值是1,那么对方都必胜,自己就只能获得必败态;但是,只要win(int((n + 1)/2))、win(int((n + 1)/2) + 1)、win(int((n + 1)/2) + 2)、…、win(n – 1)中存在某个值是0,那么自己可以获得必胜态。

     

    ² 换句话来说:

     

    ² 一旦发现某个r值使得win(r) = 0,则从win(r + 1)到win(2 * r)的值都是1;

     

    ² 一旦发现win(r + 1)到win(2 * r)的值都是1,则win(2 * r + 1) = 0;

     

    ² ……

     

    ² 上述分析很重要,搞明白上述分析后,

     

    ² r = 1时,已知win(1) = 0,能推出win(2) = 1,以及win(3) = 0。

     

    ² r = 3时,由win(3) = 0,又能推出win(4) = win(5) = win(6) = 1,以及win(7) = 0。

     

    ² r = 7时,由win(7) = 0,又能推出win(8) = win(9) = … = win(14) = 1,以及win(15) = 0。

     

    ² r = 15时,由于win(15) = 0,又能推出win(16) = win(17) = … = win(30) = 1,以及win(31) = 0。

     

    ² r = 31时,由于win(31) = 0,又能推出win(32) = win(33) = … = win(62) = 1,以及win(63) = 0。

     

    ² ……

     

    ² 自此,由数学归纳法可以证明,只有n = (int)pow(2, m) – 1时(m为非负整数),win(n) = 0,当前游戏者面临必败态,除此之外,当前选手都能面临必胜态!

     

    ² 求win函数的代码我写了一个,只有寥寥数行。

     

    ² 农大公布的标准程序采用的是递归的方法,我个人认为没有我上面的算法效率高,我的算法实质上就是判断n+1是不是构成2的某个非负整数次方幂。

     

    ² 猪县长同学写的程序可能效率更高,他采用的是预制表的办法,将问题规模内的2的所有整数次方幂-1的值放在一个向量或者数组中,然后用类似二分查找的办法去找,找到了就是先手必胜,否则先手必败。

     

    ² #include <iostream>

     

    ² using namespace std;

     

    ² int Win(int n)

     

    ² {

     

    ²     int m = log(n + 1) / log(2);

     

    ²     int k = pow(2, m);

     

    ²     if(k == n + 1) return 0; //先手必败

     

    ²     return 1; //先手必胜

     

    ² }

     

    ² int main()

     

    ² {

     

    ²     int n;

     

    ²     cin >> n;

     

    ²     while( n > 0)

     

    ²     {

     

    ²         if(Win(n)) cout << “Alice\n”;

     

    ²         else cout << “Bob\n”;

     

    ²         cin >> n;

     

    ²     }

     

    ²     return 0;

     

    ² }

     

  • 相关阅读:
    Oracle 更改DBID
    Oracle 修改字段长度
    Oracle 索引
    Oracle在无法打开数据库的状态下获取DBID
    Oracle 备份脚本
    Linux crontab计划任务
    Oracle restore和recovery的区别
    Django基础
    面向对象(一)
    socket
  • 原文地址:https://www.cnblogs.com/qq78292959/p/2819848.html
Copyright © 2011-2022 走看看