初学 博弈论 又称对策论 Game Theory

博弈论 真的很有趣  ,回想起 前两天多校一道题的题解 所有不公平的游戏都存在必胜的玩法

 与人斗其乐无穷  https://vjudge.net/contest/241983#overview 博弈论专题

一)巴什博奕(Bash Game):有n个石头,Alice和Bob轮流取石头,每次取的石头不能超过m个,Alice开始取,最后取完的赢,两个人都是以最优的方案取,求最终赢的是谁

 假设 n=m+1个,Alice 先取,无论她取多少个(>=1),Bob 都能一次性把剩下的取完,so ? Alice必输 

 情况就可以推广变成 如果 n= (m+1)*k  也就是 n%(m+1)==0 时,Alice 必输 ,反之 Alice 必赢  

代码的话 实现很简单  hdu2188  hdu2149  

 1  #include<cstdio>
 2 int main(){
 3     int t,n,m;
 4     scanf("%d",&t);while(t--){
 5         scanf("%d%d",&n,&m);
 6         if(n%(m+1)==0)printf("Bob
");
 7         else printf("Alice
");
 8     }
 9     return 0;
10 }
View Code

二)威佐夫博奕(Wythoff Game):有两堆石头,Alice和Bob轮流从某一堆取任意多的石头,或者从两堆中取相同多的石头,每次至少取一个石头, Alice开始取,最后取完的赢,两个人都是以最优的方案取,求最终赢的是谁(hdu 1846  hdu1527  51NOd1185)

就首先引入奇异局势,也就是当面临其一局势的状态必输。 比如(0,0)就是一个奇异局势,面临这种情况必输。  接下来的我就不懂了。。。。- = .=    十分钟前 ,我现在模拟了几组,懂了

前几个奇异局势有(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20) ...

性质就是每个自然数都会出现在一个且唯一的一个奇异局势里面 ,非奇异局势可以经过适当操作转化为奇异局势 

   两个数 不是奇异局势 就是 非奇异局势  ,当Alice面对奇异局势时,必输,反之,必赢

不是很懂的话 随便想两个数 模拟试试 多试几组就好了

三)尼姆博弈

有三堆石头,Alice和Bob轮流从其中一堆中取任意多的石头,每次至少取一个石头, Alice开始取,最后取完的赢,两个人都是以最优的方案取,求最终赢的是谁

奇异局势:第一个奇异局势是(0,0,0),面对这种情况的一定输。

设有a,b,c三堆石头,当a^b^c == 0时就是奇异局势,必输。
非奇异局势一定能变为奇异局势,奇异局势一定能变成非奇异局势。
证明:
当a^b^c != 0时,a+b+c != 0,即还有石头。
令a^b = x1,a^c = x2,b^c = x3,一定会存在至少下面的一种情况:
x1 < c,x2 < b,x3 < a。
假设x1 < c ,令c‘ =c – (c – x1) = x1,
得a^b^c’ == 0。
即非奇异局势变为奇异局势。

当a^b^c == 0 时,即a^b = c
只需要变成其中的任意一个数就可以当a^b != c。
即奇异局势变为非奇异局势。
由于(0,0,0)是最终得到的奇异局势,并且没有石头可取,所以面对a^b^c== 0的状态必输。

  就异或为零和不为零情况就是了 

http://acm.hdu.edu.cn/showproblem.php?pid=1907   多组输入 一个棋盘上黑白的位置 问谁嬴谁输 不懂为啥得 a-b-1 ???

走子方式,于是我们通过sg函数的定义,爆出sg函数值就发现sg函数值就是两个棋子的距离的绝对值减1.其实这个还是可以理解的,因为我们看当两个棋子相邻的时候,已经是一个必败态了。 ???

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
int main()
{
    int n,m,a,b;
    while(scanf("%d%d",&n,&m)!=EOF){
        int cnt =0,flag =0;
        for(int i=0;i< n;i++){
            scanf("%d%d",&a,&b);
            if(a < b)std::swap(a,b);
            if(a > 1)flag++;cnt ^= abs(a-b-1);//???
        }
        printf(cnt==0 ?"BAD LUCK!
":"I WIN!
");
        
    }
    return 0;
}
View Code

 SG函数

SG函数可以看成各个Nim游戏的和,将问题分成各种Nim问题,从而简化了问题。

例如:有三堆石头,每次能取的数量为斐波拉系数,即只能取1,2,3,5,8,13,21……这些石头的数量。

定义mex运算:表示最小的不属于这个集合的数。例如:mex{0,1,2,3} = 4、mex{0,1,3,4,5} = 2、mex{} = 0

对于任意的x,Sg(x) = mex{S},S为x的后继状态数值的集合,假设有三个状态Sg(a)、Sg(b)、Sg(c)。那么Sg(x) = mex{Sg(a),Sg(b),Sg(c)}。

设有三堆石头为x、y、z这三堆。那么只要判断Sg(x)^Sg(y)^Sg(z) == 0就行,当成立时就说明面对的是奇异局势,必输,反正必赢。 

/* 摘自jumping_frog 博客 

首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如

mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。

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

sg[x]。

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

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

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

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

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

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

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

以此类推.....

   x         0  1  2  3  4  5  6  7  8....

sg[x]      0  1  0  1  2  3  2  0  1....

计算从1-n范围内的SG值。

f(存储可以走的步数,f[0]表示可以有多少种走法)

f[]需要从小到大排序

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

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

3.可选步数为一系列不连续的数,用getSG()计算

*/

   不懂i  SG函数  先放着 过段时间再来看  如果是费波纳茨序列的话 只需数组开16 第十六个费波纳茨数就1196 

 打表  和  dfs   

// 获得SG数组函数模板,t代表f数组的个数,n代表要求的sg数组上限
// f数组就是能取的个数(对于此题就是Fibonacci数列
// 有时,对于t已知就不需要单独传参
void get_sg(int t,int n)
{
    int i,j;
    memset(sg,0,sizeof(sg));
    for(i=1;i<=n;i++)
    {
        memset(mex,0,sizeof(mex));
        // 对于属于g(x)后继的数置1
        for( j=1 ;j<=t && fib[j]<=i ;j++ )
            mex[sg[i-fib[j]]]=1;
        // 找到最小不属于该集合的数
        for( j=0 ; j<=n ; j++ )
            if(!mex[j])
                break;
        sg[i] = j;
    }
}
View 打表 Code
//注意 S数组要按从小到大排序 SG函数要初始化为-1 对于每个集合只需初始化1遍
//n是集合s的大小 S[i]是定义的特殊取法规则的数组
int s[N],sg[N],n;
bool vis[N];
int dfs_SG(int x){
    if(sg[x] != -1)
        return sg[x];
    memset(vis,0,sizeof(vis));
    for(int i = 0; i < n; ++i){
        if(x >= s[i]){
            dfs_SG(x-s[i]);
            vis[sg[x-s[i]]] = 1;
        }
    }
    for(int i = 0;; ++i){
        if(!vis[i]){
            e = i;
            return sg[x] = i;
        }
    }
}
View dfs Code
#include <bits/stdc++.h> // hdu 1848 
using namespace std;
const int N = 1010;
int sg[N], mex[N];
int fa[55]; 
void getSG(int n) {
    for(int i = 1; i <= n; i ++) {
        memset(mex, 0, sizeof(mex));
        for(int j = 1; fa[j] <= i; ++ j)
            mex[sg[i-fa[j]]] = 1;
        for(int j = 0; ; j ++) {
            if(!mex[j]) {
                sg[i] = j;
                break;
            }
        }
    }
}
int main() {
    int a, b, c;
    fa[1] = 1, fa[2] = 2;
    for(int i = 3; i < 55; i ++) fa[i] = fa[i-1] + fa[i-2];
    getSG(1000);
    while(cin >> a >> b >> c) {
        if(a==0 && b==0 && c==0)break;
        if(sg[a] ^ sg[b] ^ sg[c])printf("Fibo
");
        else printf("Nacci
");
    }
    return 0;
}
View Code
#include <bits/stdc++.h>
using namespace std;

const int  M = 105;
int sg[M][M], vis[M], n, m;

void fun()
{
  memset(sg, 0, sizeof(sg));

  for (int i = 1; i <= m; i++)
    for (int j = 1; j <= m; j++)
    {
      if (i == j) continue;

      memset(vis, 0, sizeof(vis));

      if (i < j) {
        for (int k = i + 1; k < j; k++)
          vis[ sg[k][j] ] = 1, vis[ sg[i][k] ] = 1;
      }
      else if (i > j) {
        for (int k = j + 1; k < i; k++ )
          vis[ sg[k][j] ] = 1, vis[ sg[i][k] ] = 1;
      }

      for (int k = 0;; k++)
        if (!vis[k])  { sg[i][j] = k; break; }
    }
}

int main()
{
  int a, b;
  while (cin >> n >> m)
  {
    int ans = 0;
    fun();
    while (n--) {
      scanf("%d%d", &a, &b);
      ans ^= sg[a][b];
    }
    if (ans == 0) cout << "BAD LUCK!" << endl;
    else cout << "I WIN!" << endl;
  }
}
View 同上 一道题 Code

hdu1730  hdu1848  hdu1536 hdu2873

http://acm.hdu.edu.cn/showproblem.php?pid=1907

原文地址:https://www.cnblogs.com/163467wyj/p/9399105.html