1. 程式人生 > >初學 博弈論 又稱對策論 Game Theory

初學 博弈論 又稱對策論 Game Theory

運算 pre 通過 algorithm 一個數 輸入 情況 由於 初始化

博弈論 真的很有趣 ,回想起 前兩天多校一道題的題解 所有不公平的遊戲都存在必勝的玩法

與人鬥其樂無窮 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\n");
 7         else printf("Alice\n");
 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!\n":"I WIN!\n");
        
    }
    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\n");
        else printf("Nacci\n");
    }
    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

初學 博弈論 又稱對策論 Game Theory