1. 程式人生 > >理論: 博弈2: 巴什博奕(Bash Game)

理論: 博弈2: 巴什博奕(Bash Game)

巴什博奕基礎情形

只有一堆n個物品,兩個人輪流從這堆物品中取物,規定每次至少取一個,最多取m個。最後取光者得勝。

如果n = m + 1; 我們假設第一個人拿走了k個, 還剩下 m + 1 - k。 因為1<=(m + 1 - k)<= m, 所以, 剩下的部分一定可以被第二個人一次性取走。

現在我們將上述規律加一推廣:
如果n=(m+1)r+s(r∈ N,s<= m);
那麼先取者要拿走s個物品,如果後取者拿走k(k<= m)個,那麼先取者再拿走m+1-k個,結果剩下(m+1)(r-1)個,
以後保持這樣的取法,那麼先取者肯定獲勝

那麼在我方取最優解

的情況下:
現在我們就發現了巴什博弈的一個必敗態: (m + 1)* r (r∈ N);
那麼巴什博弈的必勝態也就可以反推出來: (m + 1)* r + k (1<=k<= m, r ∈N);

現在我們換一個角度來考慮這個問題:
如果物品數量隨機,那麼先手一方勝利的概率是m/(m+1)後手方勝利的概率是1/(m+1)

對於上述的問題, 我們可以用mod的方法快速的求出答案(其實三種博弈的最優美之處就是能用 %^&這三個符號完美解答)

`

巴什博弈演變

硬幣問題1

Alice和Bob在玩這樣的一個遊戲, 給定k個數組a1,a2,a3,……ak。 一開始, 有x枚硬幣, Alice和Bob輪流取硬幣, 每次所取硬幣的枚數硬頂要在 a1,a2,a3,……ak之中。 Alice先取, 取走最後一枚硬幣的一方獲勝。 當雙方都採取最優策略的時候, 誰會獲勝? 題目假定a1,a2,a3,……ak之中一定有1。

下面我們來分情況討論到達自己時, 還有j枚硬幣的勝負情況:

1.題目規定, 取光所有的硬幣就獲勝, 這等價於輪到自己時如果沒有硬幣就失敗, 因此j = 0時是必敗態。
2.如果對於某個i(i<=i<= k), 就j - ai是必敗態, 那麼j就是必勝態(如果當前有j枚硬幣, 只要取走ai枚對手就必敗→自己必勝);
3.如果對於任意的i(1 <= i <= k), j - ai都是必敗態的話, j就是必敗態(無論對手這麼走, 對手都必勝, 自己必敗)

根據這些規則, 我們都能利用動態規劃演算法按照j從小到大的順序計算必勝態。, 只要看x是必勝態還是必敗態, 我們就知道誰會取勝;

通常來說, 通過考慮每個小狀態的取勝條件, 判斷必勝條件和必勝態, 是有勝負的遊戲的基礎;

上程式碼

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

int x,k;
bool win[100000];
int a[100000];

void Bash()
{//輪到自己沒有硬幣則必敗;
    win[0] = false;

    for (int i = 1; i <= x; i++)
    {//如果可以讓對手達到必敗態, 則自己必勝;
        win[i] = false;
        for (int j = 0; j < k; i++)
            win[i] |= (a[j] <= i && !win[i - a[j]]); // |=   真真為真 真假為真  假假為假
    }
}

int main(void)
{
    while (scanf("%d %d", &x, &k) != EOF)
    {
        for (int i = 0; i < k; i++)
            scanf("%d", &a[i]);
        sort(a, a + k);
        memset(win, 0, sizeof(a));
        Bash();
        if (win[x])
            printf("Alicce!\n");
        else
            printf("Bob!\n");
    }
    return 0;
}

硬幣問題2

n枚硬幣排成一個圈。Alice和Bob輪流從中取一枚或者兩枚硬幣, 不過, 取兩枚時這兩枚必須是連續的。硬幣取走之後留下空位,想個空位的硬幣被認為是不連續。 A開始先取, 取走最後一枚硬幣的一方獲勝。雙方都採取最優策略的時候誰會取勝?

這裡寫圖片描述

按照上面問題的想法, 我們來拆分一下問題;

這裡寫圖片描述

首先我們是想一下如下的情況。 將所有的剩餘硬幣分成完全相同的組別, 這個是必勝狀態還是必敗狀態?

事實上這是必敗態, 無論採取什麼策略, 對手只要在另一組中使用相同的策略, 就有回到了相同分組的狀態, 而最終的相同分組狀態就是(0,0);

接下來, 我們迴歸正題。A在第一取走一枚或者兩枚硬幣之後,稱圈的硬幣就變成了長度為n - 1或者是n - 2 的。 Bob只要在中間位置根據鏈的奇偶性, 在鏈的中間位置取走1或者2枚, 就可以把所有的硬幣分成長度相同的兩個鏈。

這裡寫圖片描述

正如我們所說的,這是必敗態, 也就是說, Alice必敗, Bob必勝,只不過, 當n <= 2, Alice可以一步取光, 所以勝利的是Alice, 除此之外, 在都進行最優解的情況下, Alice必敗。

int k;

if(k <= 2)
    printf("Alice!\n");
else
    printf("Bob\n");

Euclid’s Game(poj 2348)

題目大意:

給定兩個整數a, b。Alice和Bob輪流用較大的數減去較小的數的整數倍, 並且相減之後的值不小於0。 如果在自己的回合之內, 相減之後的結果為零, 那麼獲勝。Alice先手。

根據上述兩個問題我們有兩個規律可以用上:1.將大問題劃分為小問題在進行解決 2.尋找開始(或結束)位置的必敗態(或必勝態)在加以正推(或倒推);

劃分: 我們按照後續處理的差別, 將其分為3類:
1.b < a, 我們將a b 進行交換, 使得b恆大於a;
2.a > (b - a);
3.a < (b - a) ;

對於第一種情況, 為了便於後續處理, 我們將其認為是預設情況;
對於第二種情況, 我麼可以選擇在b 的基礎上減去 a, 或者減去2a和以上;(這一種是有餘地的方案)
對於第三種情況, 我們僅僅只有減去a這一種方法;

對於第一種情況:預設的調整方式, 我們用來調整a,b代表的值, 在這裡不用討論(這裡實際指的就是swap函式的呼叫過程)
對於第三種情況, 僅僅只有一種方案: 繼續向下迭代, 直到出現最終結局;
對於第二種情況: 假設x是使b - ax < a成立的整數, 那麼在第二種情況中又可以分為兩種情況 I. b - ax; II.b - a(x - 1);

下面我們討論 二.I: 這種情況和第三種情況類似, 只能向下迭代, 尋找最終的結果;
對於情況二.II:這種情況的後繼情況就是情況就只有b - ax這一種情況, 很巧, 這又回到了情況三。而經過了兩次轉換, 這種情況依舊是必勝態;

int a, b;

bool flag = true;
while(true)
{
    if(a > b)
        swap(&a, &b);

    if(b % a == 0)
        break;

    if(b - a > a)
        break;

    b -= a;
    flag = !flag; 
}
if(flag)
    printf("Alice!\n");
else
    printf("Bob!\n");