博弈論 學習筆記

博弈論算法學習記錄

學習資料

巴什博奕(Bash Game)

博弈原題

最簡單的巴什博奕題大概就是搶三十了,兩個人分別報數,每人一次可以報1~4個數,先報數到30的人贏。

這個游戲是典型的后手必勝游戲。首先,當一個人報數到了25,那么他必然會贏,因為不論下一個人報數多少,他都能湊足30。既然25 -> 30是這樣,那么20 -> 25也是這樣,凡是報數到20的人下一回合都能報數到25。以此類推,我們發現,后手必然能夠報數到5,然后是10152025

這樣就得到了是后手必勝。

博弈原理

我們分析一下其中的原理,為什么是5?因為 $5=4+1$ 。那么我們就得到了巴什博奕完善的表達形式:

有一堆n個石子,兩人在自己的回合可以任意取1~m個石子,先取完者勝。那么如果 $n=k\times(m+1)$ ,后手必勝,否則先手必勝。

威佐夫博弈(Wythoff Game)

博弈原理

有兩堆石子,兩人可以在自己的回合取某一堆中任意數量的石子,也可以同時從兩堆中任意取相同數量的石子,先取完者勝。

推導過程看不懂。但大體過程是這樣的:

(a, b)表示兩堆石子數目,第一必敗態毋庸置疑是(0, 0);第二必敗態是(1, 2),因為先手無論如何取,后手下一步都能取完所有石子。

然后是第三必敗態(3, 5),因為無論先手如何取,后手都能走到下一個必勝態。以此類推可以得到:

序號 必敗態
0 (a, b)
1 (0, 0)
2 (1, 2)
3 (3, 5)
4 (4, 7)
5 (6, 10)
6 (8, 13)
7 (9, 15)
8 (11, 18)

觀察樣例,很難得出結論,但是樣例數很多之后我們發現了其中的規律:

$$
a = \frac{\sqrt{5}+1}{2}\times (b-a)
$$

博弈應用

判斷先\后手輸贏

很好解決,直接判斷ab就可以了。

求先手輸贏,輸出第一次取法

先考慮兩堆同時取,這樣差值是不變的。如果只能取一堆,那么枚舉差值即可。

尼姆博奕(Nimm Game)

博弈原題

有三堆石子,兩人可以在自己的回合取某一堆中任意數量的石子,先取完者勝。

我們依然用(a, b, c)表示當前狀態。首先我們知道(0, 0, 0)是必敗態。第二種必敗態是(0, n, n),這種狀態,無論先手那多少,后手都可以從另一堆中拿同樣數量。

然后直接給出規律吧……完全看不出來的。

必敗態的充要條件是a ^ b ^ c = 0

博弈推廣

現在有n堆石子,其他規則不變。這樣的博弈依然服從尼姆博弈的規律。

我們把滿足所有值的異或值是0的狀態稱為平衡狀態,平衡狀態為必敗態。并且平衡狀態只能轉化為非平衡狀態,非平衡狀態可以轉化為平衡狀態。

那么我們把博弈規則改一改,改為先取完者負。

這樣初看很復雜,但是實際依然存在規律。首先依然按照尼姆博弈的規律取,直到有某一堆物品數量大于1,其他堆全部為1的狀態,將這堆取空或者取為1,使數量為1的堆的數目變為奇數即可。因為操作需要滿足平衡狀態,所以不會面對這種情況,這樣就可以判斷勝負了。

例題(HDU 1850)

思路

求尼姆博弈先手是否能勝,如果能,第一步有幾種走法。

思路很簡單,只要看當前是否是非平衡狀態,下一步哪個堆可以轉化為平衡狀態即可。

代碼
#include <bits/stdc++.h>
using namespace std;

int main(){
    int n, num[100+7];
    while (scanf("%d", &n), n) {
        int sum = 0, cnt = 0;
        for (int i = 0; i < n; ++i) {
            scanf("%d", num+i);
            sum ^= num[i];
        }
        for (int i = 0; i < n; ++i)
            if (num[i] > (sum^num[i])) ++cnt;
        printf("%d\n", cnt);
    }
    return 0;
}

反尼姆博奕例題(HDU 1905)

思路

與正常的尼姆博弈幾乎相同,只是要特判奇數個1和偶數個1的情況。

代碼
#include <bits/stdc++.h>
using namespace std;

int n, num[50];

void solve() {
    int sum = 0, odd = 0, even = 0;
    scanf("%d", &n);
    for (int i = 0; i < n; ++i) {
        scanf("%d", num+i);
        sum ^= num[i];
        num[i] < 2 ? ++odd : ++even;
    }
    if ((sum && even) || (!sum && !even)) puts("John");
    else puts("Brother");
}

int main(){
    int t; scanf("%d", &t);
    while (t--) solve();
    return 0;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • http://acm.hdu.edu.cn/showproblem.php?pid=1525 題意:給你兩個數,每...
    Gitfan閱讀 975評論 0 0
  • 博弈論是很有意思的數學問題,如果能夠懂其道理,短短幾行代碼就能將其本質表示出來。 目前我的感受就是:在一場博弈之中...
    A黃橙橙閱讀 1,435評論 0 0
  • 有一種很有意思的游戲,就是有物體若干堆,可以是火柴棍或是圍棋子等等均可。兩個人輪流從堆中取物體若干,規定最后取光物...
    moosoo閱讀 687評論 0 0
  • 今天聽了北美培訓機構九章算法的一節公開課,做點心得筆錄。 1.首先介紹什么是博弈問題,面試中博弈問題的特點 生活中...
    小雙2510閱讀 3,181評論 0 0
  • 梅一瞬間不知所措,父親的張牙舞爪的樣子被她打轉在眼窩中的眼淚逐漸模糊,母親誠惶誠恐地低下頭沉默不語。 梅不知道自己...
    張泥稱重閱讀 514評論 0 1