【博弈论】

一曲冷凌霜 提交于 2020-02-05 22:48:58

巴什博奕       

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

          分析
                 (1)当n≤m时,由于一次最少拿1个、最多拿m个,甲可以一次拿完,先手赢。
                 (2)当n=m+1时,无论甲拿走多少个(1~m个),剩下的都多于1个、少于等于m 个,乙都能一次拿走剩余的石子,后手取胜。
上面两种情况可以扩展为以下两种情况:
                   A.如果n%(m+1)=0,即n是m+1的整数倍,那么不管甲拿多少,例如k个,乙都 拿m+1-k个,使得剩下的永远是m+1的整数倍,直到最后的m+1个,所以后拿 的乙一定赢。
                   B.如果n%(m+1)!=0,即n不是m+1的整数倍,还有余数r,那么甲拿走r个,剩下的是 m+1的倍数,这样就转移到了情况(A),相当于甲、乙互换,结果是甲赢。

           例题:

                      hdu 2147

              题意:

                      在一个m*n的棋盘内,从(1,m)点出发,每次可以进行的移动是:左移一,下移一,左下移一。然后kiki每次先走,判断kiki时候会赢(对方无路可走的时候)。

           分析:

                      我们可以把PN状态的点描绘出来:

                            可以发现 n,m 中有一个是2 的倍数,则 为先手获胜,反之,后手必胜。

                      code:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int main( )
 4 {
 5     int n,m;
 6     while(scanf("%d%d",&n,&m)&&(n!=0||m!=0))
 7     {
 8         if(n%2==0||m%2==0)
 9             printf("Wonderful!\n");
10         else
11             printf("What a pity!\n");
12     }
13     return 0;
14 }

 NIM 游戏

               通常的Nim游戏的定义是这样的:有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。

               定义P-position和N-position,其中P代表Previous,N代表Next。直观的说,上一次move的人有必胜策略的局面是P-position,也就是“后手可保证必胜”或者“先手必败”,现在轮到move的人有必胜策略的局面是N-position,也就是“先手可保证必胜”。

                   (Bouton's Theorem):对于一个Nim游戏的局面(a1,a2,...,an),它是P-position当且仅当a1^a2^...^an=0,其中^表示异或(xor)运算。

                              对于某个局面(a1,a2,...,an),若a1^a2^...^an!=0,一定存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。

                                  对于某个局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。

                SG 函数

                              必胜点和必败点的概念:

                                                 P点:必败点,换而言之,就是谁处于此位置,则在双方操作正确的情况下必败。
                                                 N点:必胜点,处于此情况下,双方操作均正确的情况下必胜。
                                     必胜点和必败点的性质:
                                                   1、所有终结点是 必败点 P 。(我们以此为基本前提进行推理,换句话说,我们以此为假设)
                                                   2、从任何必胜点N 操作,至少有一种方式可以进入必败点 P。
                                                   3、无论如何操作,必败点P 都只能进入 必胜点 N。

                                    Sprague-Grundy定理(SG定理):

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

                              SG函数:

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

                                          对于任意状态 x , 定义 SG(x) = mex(S),其中 S 是 x 后继状态的SG函数值的集合。如 x 有三个后继状态分别为 SG(a),SG(b),SG(c),那么SG(x) = mex{SG(a),SG(b),SG(c)}。 这样 集合S 的终态必然是空集,所以SG函数的终态为 SG(x) = 0,当且仅当 x 为必败点P时。

                                     计算1~n的SG函数值步骤如下:

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

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

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

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

例题: hdu 1850

思路:1)如若给出 的是必败状态:a1^a2^......^an=0,则先手不会有任何可能获得胜利;

           2)若给出的是必胜状态:a1^a2^.......^an=k,(其中k不为零),那么我们的目的是要把必胜状态

        转化为必败状态从 而使得先手胜利。若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。

code:

 1 #include<stdio.h>
 2 int main()
 3 {
 4     int x,m,a[110],i,ans;
 5     while(scanf("%d",&m),m)
 6     {
 7          x=ans=0;
 8          for(i=0;i<m;i++)
 9          {
10                 scanf("%d",&a[i]);
11                 x^=a[i];
12          }
13          for(i=0;i<m;i++)
14          ans+=(a[i]>(x^a[i]));
15          printf("%d\n",ans);
16     }
17     return 0;
18 }

 

hdu 1848

sg函数的简单应用

code:

 1 #include<cstdio>
 2 #include<string.h>
 3 using namespace std;
 4 int fib[20],sg[1010],m,n,p;
 5 int calsg(int now){
 6     int i,tem;
 7     int next[20];
 8     memset(next,0,sizeof(next));
 9     for(i=1;fib[i]<=now;i++){
10         tem=now-fib[i];
11         if(sg[tem]==-1)
12             sg[tem]=calsg(tem);
13         next[sg[tem]]=1;
14     }
15     for(i=0;;i++)
16         if(next[i]==0){
17             return i;
18         }
19 } 
20 int main(){
21     int i,j,tem;
22     fib[1]=1;
23     fib[2]=2;
24     for(i=3;i<=16;i++)   
25         fib[i]=fib[i-1]+fib[i-2];
26     memset(sg,-1,sizeof(sg));
27     while(scanf("%d %d %d",&m,&n,&p)==3 && !(m==0 && n==0 && p==0)){
28         tem=calsg(m)^calsg(n)^calsg(p);
29         if(tem==0)
30             printf("Nacci\n");
31         else 
32             printf("Fibo\n");
33     }

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!