ACM常見組合博弈遊戲

這兩天認識了幾個組合遊戲的基礎模型,希望自己能更新下去。。

Ferguson遊戲

Description

  • Initial

有兩個盒子,一個裝有 m 顆糖,一個裝有 n 顆糖,表示爲 (m, n) .

  • Step

每次清空一個盒子,將另一個盒子裏的糖轉移一些過來,並保證兩個盒子至少各有一顆糖。

  • Win

最後進行轉移糖者勝,無法轉移糖者敗。

Solve

m, n 都爲奇數,先手敗;m, n 至少一個爲偶數,先手勝。

Proof

顯然,初始狀態爲(1, 1),先手必敗;

  • 設 max(m, n) = 2,即初始狀態爲 (1, 2),(2, 1) 或 (2, 2),對於 (1, 2),(2, 1) 先手可以把 1 清空,然後將 2 分爲 (1, 1) ,先手勝;對於 (2, 2) ,先手可以把其中一個 2 清空,然後將另一個 2 分爲 (1, 1) ,先手勝。符合結論。

  • 設 max(m, n) < k 均符合結論,當 max(m, n) = k :

    • 設 m 與 n 至少一個爲偶數(假設m是偶數),則將 n 清空,把 m 分爲兩個奇數 (a, b) ,由於max(a, b) < k ,因此(a, b) 必敗,(m, n) 必勝(利用規則2);

    • 設 m 與 n 均爲奇數,則只能把其中一個數分爲一個奇數 a ,一個偶數 b ,由於max(a, b) < k ,因此對於任何的方式分解出的(a, b) 均必勝,(m, n) 必敗(利用規則1);

    • 故 max(m, n) = k 符合結論。

  • 故對於任意 (m, n) 結論成立。

chomp!遊戲

Description

  • Initial

有一個 m * n 的棋盤,棋盤的每一個格子用(x, y)表示,最左下角是(1, 1),最右上角是(m, n) ;

  • Step

每次可以拿走一個方格,並拿走該方格右邊與上邊的所有方格。

  • Win

誰拿到(1, 1)誰敗。

Solve

當 m = n = 1,先手敗;除此之外,先手均有必勝策略(先手勝)。

Proof

反證法:

假設後手能取得勝利,那麼先手可以第一步拿走(m, n),若後續回合內後手通過拿走(x, y)達到了必勝狀態,先手均可以第一步就拿走(x, y)來達到必勝狀態。
故不存在後手必勝狀態。

由於無法給出構造性證明,所以只能證明先手必勝,而不能給出廣義的必勝策略。

約數遊戲

Description

  • Initial

桌上有 n 個數字:1~n。

  • Step

兩人輪流在選擇一個桌上的數 x ,然後將 x 與 x 的約數都拿走。

  • Win

拿去最後一個數的人勝出(無法選擇數字的人失敗)。

Solve

先手有必勝策略。(先手勝)

Proof

這個遊戲是 chomp! 的思想的應用。

假設後手能取得勝利,那麼先手可以第一步拿走 1,若後續回合內後手通過拿走 x 達到了必勝狀態,先手均可以第一步就拿走 x 來達到必勝狀態。

Bash Game(巴什博弈)

Description

  • Initial

n 個物品堆成一堆。

  • Step

兩個人輪流從這堆物品中取物,規定每次至少取一個,最多取 m 個。

  • Win

最後取光者得勝。(無法取者敗)

Solve

如果 n % (m+1)0 ,則先手必勝。

Proof

  • 如果 n=m+1 , 顯然,先手無論取多少,後手均可以將剩餘物品一次全取走,所以先手敗。

  • 如果 n=k(m+1) ,我們從後手的角度來考慮,設先手第一次取走 x 個物品,那麼後手只要再取走 m+1x 個,此時剩餘物品數量變爲 (k1)(m+1) 個,一直重複這個步驟,就可以回到先手面臨 n=m+1 的局面,所以還是先手敗。相當於進行了k次 n=m+1 的遊戲。

  • 如果 n=k(m+1)+s ,先手一開始取走 s 個物品,那麼後手就會面臨 n=k(m+1) 的局面,所以先手勝。

  • 結論得證。

例題

  1. 有一個遊戲,在一個n*m的矩陣中起始位置是(1, m),走到終止位置(n, 1);遊戲規則是隻能向左,下,左下方向移動一步,先走到終點的爲獲勝者。(HDU 2147,總共有 n + m 的距離要移動,一次最多移動 2 的距離,故判斷 (n + m)%2 是否爲 0 即可)。

  2. bash博弈變形1——減法博弈:
    兩個人輪流報數,每次至少報一個,最多報十個,誰能報到100者勝。(先手必勝,第一次報1,類似:HDU 2149)
    有一個由n個石子組成的石子堆,兩名玩家輪流從中拿走石子,每次拿走石子的個數只能是集合S中的數。拿走最後一枚石子的玩家獲勝。(狀態轉移即可)

  3. bash博弈變形2:初始狀態下有石子n個,除最後一次外其他每次取物品個數必須在[p,q]之間,最後一次取硬幣的人輸。(HDU 2897)
    這題狀態稍微複雜一些,並且勝負條件與之前相反,一般bash博弈裏每次取個數可以看作在[1,m]之間,勝負手判斷爲 n % (1+m) ,因此我們可以猜想每次取[p,q]的勝負手判斷爲 n % (p+q) ,通過驗證猜想我們可以發現如下策略:

    • n=k(p+q) 時,先手第一次取 q 個,隨後的回合若後手取 x 個,先手再取 p+qx 個,那麼最後就會留給後手 p 個,先手勝。
    • n=k(p+q)+s 時,則要分情況考慮:
      1. 若 s 在[1,p] 之間,先手取 x,後手可以取 p+qx ,最後留給先手 s 個,後手勝;
      2. 若 s 在(p,p+q) 之間,先手任取 x 個 (1sx<p) ,後手取 y 個,先手可以再取 p+qy ,最後留給後手 s - x 個,先手勝;
//HDU 2897
#include<bits/stdc++.h>
using namespace std;

int main()
{
    int n , p , q ;
    while ( cin >> n >> p >> q ) {
        if ( n % ( p + q ) <= p && n % ( p + q ) ) 
            puts( "LOST" ) ;
        else
            puts( "WIN" ) ;

    }
    return 0;
}

Wythoff’s Game(威佐夫博弈)

Description

  • Initial

有兩堆石子,一堆有 m 個,另一堆有 n 個。

  • Step

雙方輪流取走一些石子,合法的取法有如下兩種:
1. 在一堆石子中取走任意多顆;
2. 在兩堆石子中取走相同多的任意顆.

  • Win

取走最後一顆石子的人爲贏家。

Solve

(1,2)(1,2)(2,1)(2,1) 視爲同一狀態,
第 k 個必敗狀態是 (\lfloor\frac{\sqrt 5 +1}{2}*k \rfloor+k,\lfloor\frac{\sqrt 5 + 1}{2}*k \rfloor)(5+12k+k,5+12k)

  • 拓展性質:

    • (m(k),n(k))=(\lfloor\frac{\sqrt 5 + 1}{2}*k \rfloor+k,\lfloor\frac{\sqrt 5 + 1}{2}*k \rfloor)(m(k),n(k))=(5+12k+k,5+12k) ,則 m(k) 也表示前 k 個必敗狀態中沒出現的自然數。

    • 每個自然數都會出現在必敗狀態中且僅會出現一次。

//HDU 1527 模板題
#include<bits/stdc++.h>
using namespace std;

int main()
{
    int a , b ;
    while ( cin >> a >> b ) {
        if ( a < b ) swap( a, b ) ;
        int k = a - b ;
        int n = (int)( k * ( sqrt(5.0) + 1.0 ) / 2 ) ;
        bool win = ( n != b ) ;
        cout << win << endl ;
    }
    return 0;
}

Proof

還是打表吧。。

const int N = 50 ;
bool win[100][100] ;
void init() 
{
    memset( win , false , sizeof win ) ;
    for ( int i = 1 ; i <= n ; i++ ) win[i][i] = true ;
    for ( int i = 1 ; i <= n ; i++ ) {
        for ( int j = i+1 ; j <= n ; j++ ) {
            if ( !win[i][j] ) {
                for ( int k = j + 1 ; k <= n ; k++ )
                    win[i][k] = win[k][i] = true ;
                for ( int k = i + 1 ; k <= n ; k++ )
                    win[k][j] = win[j][k] = true ;
                for ( int k = 1 ; k <= n ; k++ )
                    win[i+k][j+k] = win[j+k][i+k] = true ;
            }
        }
    }
    for ( int i = 1 ; i <= n ; i++ )
        for ( int j = i ; j <= n ; j++ ) {
            if ( !win[i][j] ) cout << i << ' ' << j << endl ;
        }
}

Fibonacci’s Game (斐波那契博弈)

Description

  • Initial

有一堆個數爲 n >= 2 的石子。

  • Step

雙方輪流取石子,滿足以下條件:
1. 先手不能在第一次把所有的石子取完;
2. 之後每次可以取的石子數介於 1 到對手剛取的石子數的 2 倍之間(包含 1 和對手剛取的石子數的 2 倍)。

  • Win

取走最後一個石子的人爲贏家。

Solve

如果 n 是斐波那契數,則後手勝;反之,先手勝。

\\HDU 2516 模板題
#include<bits/stdc++.h>
using namespace std;

const string win[2] = { "Second win" , "First win" } ;
long long fib[100] ;
int init() 
{
    fib[0] = 1 ; fib[1] = 1 ;
    for ( int i = 2 ; i < 100 ; i++ ) {
        fib[i] = fib[i-1] + fib[i-2] ;
        if ( fib[i] > ( 1LL << 35 ) ) return i ;
    }
    return 100 ;
}
int main()
{
    int n ; int len = init() ;
    while ( cin >> n && n ) {
        int ok = 1 ;
        for ( int i = 0 ; i <= len ; i++ ) {
            if ( fib[i] == n ) {
                ok = 0 ;
                break ;
            }
        }
        cout << win[ok] << endl ;
    }
    return 0;
}

Proof

Zeckendorf定理:任何正整數可以表示爲若干個不連續的 Fibonacci 數之和。

先手必須從 <= n / 3 的數量開始取(例如第一次取的數量 > n/3,那麼後手可以直接取完所有剩下石子)
於是。。。太繞了!
還是請看Acdream大神的吧。。http://blog.csdn.net/acdreamers/article/details/8586135

Nim遊戲(待施工)

Description

有三堆各若干個物品,兩個人輪流從某一堆取任意多的
物品,規定每次至少取一個,多者不限,最後取光者得勝。

Solve

介紹這個遊戲的太多了。。偷個懶
具體解法就是異或異或!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章