博弈论小结

【概述】
  最近的几次比赛,博弈的题目一直不少,而且博弈问题是一块比较复杂、庞大的内容,因此在这里小结一下,希望能够帮自己理清一些思路,争取也多来几个系列,呵呵。

竞赛中出现的组合游戏问题一般都满足以下特征:
    1.二人博弈游戏,每个人都采用对自己最有利的策略,并且是两个人轮流做出决策
    2.在游戏中的任意时刻,每个玩家可选择的状态是固定的,没有随机成分
    3.游戏在有限步数内结束,没有平局出现
  大部分的题目都满足上述条件,因此这里只讨论在上述条件范畴内的博弈问题。这类博弈问题,通常还有若干分类。一种是规定移动最后一步的游戏者获胜,这种规则叫做 Normal Play Rule;另一种是规定移动最后一步的游戏者输,这种规则叫做 Misere PlayRule,也称为Anti-SG游戏。此外,对于游戏的双方,如果二者博弈的规则相同,那么称为这类游戏是 对等(impartial games)的;否则称为 不平等游戏(partizan games)。当初WHU的那场比赛就是由于对于这个概念不是很清晰,导致看完题目之后就用SG定理来做,浪费了很多机时。实际上,解决不平等博弈问题的方法和普通的博弈问题(SG游戏)是有区别的,一般会采用动态规划或者surreal number。

【博弈基础知识】
  在SG游戏中,最为人熟知的是必胜必败态,也叫NP态理论。注意的是,P态对应的是先手必败态,N态对应的是先手必胜态。必胜必败态理论是:
  1. All terminal positions areP-positions
  2. From everyN-position, there is at least one move to a P-position
  3. From every P-position, every move is to anN-position
  英文的表述非常简洁清晰,而且这个理论也很好理解,如果在当前状态的下一步可以走到必败态,那么当前玩家就可以走到那个状态,把必败态推给另一方;如果所有可达状态都是必胜态,那么当前玩家无论如何走,都会把必胜态让给对方。根据必胜必败态理论,我们可以递归的求出每个状态是N态还是P态。必胜必败态理论其实已经把博弈问题转化成了一个有向图,借助图这个模型来分析问题,使得问题变得形象了许多。需要注意的是,这种SG游戏对应的有向图是无环的,因为如果有环,那么游戏双方就可能在环上不停的转换状态,游戏不能在有限步终止,这样就不满足组合游戏的特征3了。
  然而在很多时候仅仅知道某个状态是必胜还是必败是不够的,因为如果存在多个组合游戏(比如经典的Nim),对应的状态集合非常大,无法直接利用必胜必败态理论求解,因此需要用到博弈论中一个很重要的工具:SG函数。
  某个状态的SG函数值定义为当前状态所有不可达的状态编号中最小的编号,其中终止态的SG函数值是0。有了这个工具,就引入一个非常强大的定理——SG分解定理:

   多个组合游戏的SG函数值是每个组合游戏的函数值的和。(这里的和定义为异或操作)
  
  SG分解定理的证明不是很难,其实和Nim的证明很像。根据这个定理,我们就知道为什么Nim的解法是异或所有的石子个数了,因为每堆石子的SG值就是石子的个数。SG分解定理告诉我们任何SG游戏都可以转化成Nim游戏来做。
  Nim中的一个变形就是拿走最后一块石子的人算输。通过修改SG的计算规则,可以得出相同的结论(因为当石子个数是1的时候SG值为0,因此要单独处理);当然也可以利用一个叫做SJ定理的方法来做,依然是要处理当所有堆的SG值不大于1的情况。

【博弈基本模型】
  除了Nim模型,很多模型都看似复杂,最后都划归到了Nim模型上,然后利用SG分解来做的。在证明两种模型等价的时候,可以通过计算SG值判断是否相同,或者通过判断必胜策略的走法将其转化为Nim。许多模型非常的神奇,其获胜策略又千差万别,因此无法一一列举,但是掌握一些经典模型是必须的,这样通过模型的转化可以简化问题的难度。
   经典模型1:Nim变种。包括:
    (1)楼梯Nim。把奇数台阶的石子作为Nim,二者等价,因为必胜的策略是相同的。
    (2) 每次可以取k堆,这个是经典的MooreNim。它是泛化的Nim游戏。
    (3)两堆石子,每次可以取一堆或两堆,从两堆取得时候个数必须相同,谁先取完获胜。这个是著名的威佐夫博弈,跟黄金分割数有关,具体证明不是很清楚,但是用SG值打表可以找出规律。代码如下:
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;

int main()
{
    const double k = (sqrt(5.0) + 1) / 2.0;
    int a, b, t;

    while (scanf("%d %d", &a, &b) == 2)
    {
        if (a > b)
            swap(a, b);
        t = b - a;
        if (a == (int)(t * k))
            puts("0");
        else
            puts("1");
    }

    return 0;
}


    (4) SubtractionGames。一种通用的Nim游戏,每次从可用状态集合中选择下一步的状态,有很多变形,核心思想还是计算SG函数值。
    (5)Take-and-Break Game。每次把局面分成多个Nim子游戏,利用SG分解定理求出对应的SG值。
   经典模型2:翻硬币游戏(Coin TurningGame)
    (1) 一维的翻硬币游戏,每次可以翻1个或两个。通过单独考虑每个可以翻的硬币发现,Coin TurningGame的SG值和Nim等价,因此两个模型等价。需要注意的是,许多翻硬币游戏根据题目的要求,一般编号从0开始。
    (2)一维的翻硬币游戏,每次可以翻1个或两个,限定了翻第二枚硬币的范围,那么就和Subtraction Game等价了。
    (3)一维的翻硬币游戏,每次可以翻1个、2个或3个,这个游戏叫做Mock Turtles,有一个神奇的规律,是Odious Number序列。
    (4)高维的翻硬币游戏,需要用到Nim积和Tartan定理。
  翻硬币模型的变化更多,很多模型都有一些奇妙的规律,需要打表才能发现。
   经典模型3:删边游戏(Green Hackenbush)
    (1)树的删边游戏:Colon原理证明这种模型和Nim依然是等价的,多个叉的SG值异或就是对应根节点的SG值。

    (2)无向图删边游戏:利用Fursion定理收缩圈,然后就转换成树的删边游戏了,不过这个定理还不会证。

*****************************************************************************************************************************************************************************************

组合博弈的通解就是sg函数,学习了sg函数之后一直没有咋用过。

学习博弈的可以在nyoj上面做10道取石子题目,作为了对博弈也就有一定理解了。

用的时候注意初始的时候只要初始sg[0]=0;

其他都通过函数求解。

这里贴一个求解sg函数的模板。

    int sg[N];  
    bool hash[N];  
    void sg_solve(int *s,int t,int N)   //N求解范围 S[]数组是可以每次取的值,t是s的长度。  
    {  
        int i,j;  
        memset(sg,0,sizeof(sg));  
        for(i=1;i<=N;i++)  
        {  
            memset(hash,0,sizeof(hash));  
            for(j=0;j<t;j++)  
                if(i - s[j] >= 0)  
                    hash[sg[i-s[j]]] = 1;  
            for(j=0;j<=N;j++)  
                if(!hash[j])  
                    break;  
            sg[i] = j;  
        }  
    }  

用set容器实现的方法,原理一样。oj上容易超时

void sg_solve()  
{  
    memset(sg,0,sizeof(sg));  
    for(int i=1;i<N;i++)  
    {  
        set<int> v;  
        for(int j=0;j<t;j++)  
            if(i - s[j] >= 0)  
                v.insert(sg[i-s[j]]);  
        int g=0;  
        while(v.count(g)!=0)  
            g++;  
        sg[i]=g;  
    }  
} 

通过一道题目说一下。

hdoj 1536 和pku 2960 S-Nim

题意就是给出一个数组s。为每次可以取石子的数目。

然后给你n堆石子每堆si。求解先手能不能赢!标准的sg函数用法题目。

代码:

    #include<stdio.h>  
    #include<string.h>  
    #include <string>  
    #include <iostream>  
    using namespace std;  
      
    const int N = 10008;  
    int s[108],t;  
    int sg[N];  
    bool hash[N];  
    void sg_solve(int *s,int t,int N)   //N求解范围 S[]数组是可以每次取的值,t是s的长度。  
    {  
        int i,j;  
        memset(sg,0,sizeof(sg));  
        for(i=1;i<=N;i++)  
        {  
            memset(hash,0,sizeof(hash));  
            for(j=0;j<t;j++)  
                if(i - s[j] >= 0)  
                    hash[sg[i-s[j]]] = 1;  
            for(j=0;j<=N;j++)  
                if(!hash[j])  
                    break;  
            sg[i] = j;  
        }  
    }  
      
    int main()  
    {  
        int i,j,n,m,h;  
        while(scanf("%d",&t),t)  
        {  
            string ans="";  
            for(i=0;i<t;i++)  
                scanf("%d",&s[i]);  
            sg_solve(s,t,N);  
            scanf("%d",&n);  
            for(i=0;i<n;i++)  
            {  
                scanf("%d",&m);  
                int res = 0;  
                for(j=0;j<m;j++)  
                {  
                    scanf("%d",&h);  
                    res ^= sg[h];  
                }  
                ans+=res?'W':'L';  
            }  
            cout<<ans<<endl;  
        }  
        return 0;  
    }  

本文转载处:http://www.cppblog.com/sdfond/archive/2010/02/06/107364.html
http://blog.csdn.net/y990041769/article/details/21406335

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值