zoukankan      html  css  js  c++  java
  • 【博弈论】

    巴什博奕       

                只有一堆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!
    ");
    10         else
    11             printf("What a pity!
    ");
    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
    ",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
    ");
    31         else 
    32             printf("Fibo
    ");
    33     }
  • 相关阅读:
    【linux 爱好者群】程序猿的那些聊天记录
    开发技巧记录
    tcmalloc 内存分析
    mktime很慢就自己去实现一个吧
    bash变量常用技巧
    文本处理sed常用操作
    【TED】如何掌握你的自由时间
    vim粘贴代码问题
    工作方式的反思-20170319
    【one day one linux】find 用法详解小记
  • 原文地址:https://www.cnblogs.com/lirh04/p/12266978.html
Copyright © 2011-2022 走看看