1. 程式人生 > >博弈知識整理

博弈知識整理

分享圖片 con 求和 tar 自然數 swa b- 出現 發現

博弈知識整理

一. 巴什博奕(Bash Game):

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

顯然,如果n=m+1,那麽由於一次最多只能取m個,所以,無論先取者拿走多少個,後取者都能夠一次拿走剩余的物品,後者取勝。因此我們發現了如何取勝的法則:如果n=(m+1)*r+s,(r為任意自然數,s≤m),那麽先取者要拿走s個物品,如果後取者拿走k (≤m)個,那麽先取者再拿走m+1-k個,結果剩下(m+1)*(r-1)個,以後保持這樣的取法,那麽先取者肯定獲勝。總之,要保持給對手留下(m+1)的倍數,就能最後獲勝。

若為最後取光者敗其實就是最後取光n-1個者勝。

例題:HDU 4764 -- Stone (最後取光者敗)

AC代碼:

技術分享圖片
 1 #include <iostream>
 2 using namespace std;
 3 
 4 int main() {
 5     int n, k;
 6     while (cin >> n >> k && (n || k)) {
 7         if ((n - 1) % (k + 1)) cout << "Tang" << endl;
 8         else cout << "Jiang" << endl;
9 } 10 return 0; 11 }
View Code

二. 威佐夫博弈(Wythoff Game):

有兩堆各若幹個物品,兩個人輪流從某一堆或同時從兩堆中取同樣多的物品,規定每次至少取一個,多者不限,最後取光者得勝。

這種情況下是頗為復雜的。我們用(ak,bk) (ak ≤ bk , k = 0,1,2,…,n)表示兩堆物品的數量並稱其為局勢如果甲面對(0,0),那麽甲已經輸了,這種局勢我們稱為奇異局勢。前幾個奇異局勢是:(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)。

可以看出,a0=b0=0,ak是未在前面出現過的最小自然數,而 bk

= ak +k,奇異局勢有如下三條性質:

1. 任何自然數都包含在一個且僅有一個奇異局勢中。

由於ak是未在前面出現過的最小自然數,所以有ak > ak-1 ,而 bk= ak + k > ak-1 + k - 1 = bk-1 > ak-1 ,即bk也未在前面的數中出現過,所以性質1成立。

2. 任意操作都可將奇異局勢變為非奇異局勢。

事實上,若只改變奇異局勢(ak,bk)的某一個分量,那麽另一個分量不可能在其他奇異局勢中,所以必然是非奇異局勢。如果使(ak,bk)的兩個分量同時減少,則由於其差不變,不可能是其他奇異局勢的差,因此也是非奇異局勢。

3. 采用適當的方法,可以將非奇異局勢變為奇異局勢。

假設面對的局勢是(a,b),若 b = a,則同時從兩堆中取走 a 個物體,就變為了奇異局勢(0,0);如果a = ak ,b > bk,那麽,取走b – bk個物體,即變為奇異局勢;如果 a = ak , b < bk ,則同時從兩堆中拿走 ak – ab-a個物體,變為奇異局勢(ab-a,b + ab-a – ak);如果a > ak ,b = bk,則從第一堆中拿走多余的數量a – ak 即可;如果a < ak ,b = bk,分兩種情況,第一種,a = aj (j < k),從第二堆裏面拿走 b – bj 即可;第二種,a = bj (j < k),從第二堆裏面拿走 b – aj 即可。

從如上性質可知,兩個人如果都采用正確操作,那麽面對非奇異局勢,先拿者必勝;反之,則後拿者取勝。

那麽任給一個局勢(a,b),怎樣判斷它是不是奇異局勢呢?我們有如下公式:

ak = (int)(k * (1 + √5) / 2),bk = ak + k (k=0,1,2,…,n 方括號表示取整函數);

我們可以用反證法,假定(a, b)為奇異局勢,那麽k = b - a,套入上面的公式得出ak,必有a == ak,否則(a, b)不為奇異局勢。

例題:HDU 1527 -- 取石子遊戲 (裸題)

AC代碼:

技術分享圖片
 1 #include <iostream>
 2 #include <cmath>
 3 using namespace std;
 4 
 5 int main() {
 6     int a, b;
 7     while (cin >> a >> b) {
 8         if (a > b) swap(a, b);
 9         int ak = (int)((b - a) * (1 + sqrt(5)) / 2);
10         if (ak == a) cout << 0 << endl;
11         else cout << 1 << endl;
12     }
13     return 0;
14 }
View Code

三. 尼姆博弈(Nimm Game):

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

奇異局勢:如果每堆物品數量分別為a1, a2, ....., an,且a1 ^ a2 ^ ...... ^ an = 0,則此局勢為奇異局勢。

至於為什麽所有數異或後等於0就是奇異局勢,網上找不到任何解釋,自己試著分析也感覺十分復雜,如果有人能找到合理解釋還請分享。(其實也不必過於糾結,就像上面的威佐夫博弈也借助了一條搞不懂的公式來判斷奇異局勢)

Bouton定理:先手能夠在非平衡尼姆博弈中取勝,而後手能夠在平衡的尼姆博弈中取勝。(這裏說的平衡狀態就是奇異局勢)

性質1:每個非奇異局勢都可以一步到達奇異局勢

檢查Nim和X的二進制表示中最左邊一個1,則隨便挑一個該位為1的物品堆Y,根據Nim和進行調整(0變1,1變0)即可。例如Nim和為100101011,而其中有一堆為101110001,為了讓Nim和變為0,只需要讓這一堆變為001011010即可。(這裏約定對原來所有堆的物品數異或後得到的數為Nim和)

性質2:每個奇異局勢一步後必為非奇異局勢

由於只能改變一堆的物品,不管修改它的哪一位,Nim的對應位一定不為0,不可能是奇異局勢。 這樣就證明了Bouton定理。 Nim博弈中如果規定最後取光者輸,情況是怎樣的? 初看起來問題要復雜很多(因為前面只要創造奇異局勢給對手就可以,現在無法迫使對手一直創造奇異局勢給自己),但對於Nim遊戲來說,幾乎是一樣的: 首先按照普通規則一樣的策略進行,直到恰好有一個物品數大於1的堆x(這種狀態其實就是非奇異局勢,和普通規則一樣一直讓自己面對非奇異局勢終會達到這種狀態)。在這樣的情況下,只需要把堆x中的物品拿得只剩1個物品或者拿完,讓對手面臨奇數堆物品,這奇數堆物品每堆恰好1個物品,這樣的狀態顯然對手是必敗的。 所以如果有大於1的堆,先手取勝與否還是看初始局勢是否為奇異局勢;如果所有堆都為1,則直接求和判斷奇偶性。 例題:HDU 1907 -- John (最後取光者敗) AC代碼: 技術分享圖片
 1 #include <iostream>
 2 using namespace std;
 3 
 4 int main() {
 5     int t; cin >> t;
 6     while (t--) {
 7         bool flag = false;
 8         int n, a, ans = 0; cin >> n;
 9         for (int i = 0; i < n; i++) {
10             cin >> a;
11             ans ^= a;
12             if (a > 1) flag = true;
13         }
14         if (flag) {
15             if (ans) cout << "John" << endl;
16             else cout << "Brother" << endl;
17         } else {
18             if (n & 1) cout << "Brother" << endl;
19             else cout << "John" << endl;
20         }
21     }
22     return 0;
23 }
View Code

四. 斐波那契博弈(Fibonacci Nim):

有一堆物品,兩人輪流取物品,先手最少取一個,至多無上限,但不能把物品取完,之後每次取的物品數不能超過上一次取的物品數的二倍且至少為一件,取走最後一件物品的人獲勝。

這個遊戲叫做Fibonacci Nim,肯定和Fibonacci數列:f[n]:1,2,3,5,8,13,21,34,55,89,… 有密切的關系。如果試驗一番之後,可以猜測:先手勝當且僅當n不是Fibonacci數。換句話說,必敗態構成Fibonacci數列

這裏需要借助“Zeckendorf定理”(齊肯多夫定理):任何正整數可以表示為若幹個不連續的Fibonacci數之和

先看看FIB數列的必敗證明:

1、當i=2時,先手只能取1顆,顯然必敗,結論成立。

2、假設當i<=k時,結論成立。

則當i=k+1時,f[i] = f[k]+f[k-1]。

則我們可以把這一堆石子看成兩堆,簡稱k堆和k-1堆。

(一定可以看成兩堆,因為假如先手第一次取的石子數大於或等於f[k-1],則後手可以直接取完f[k],因為f[k] < 2*f[k-1])

對於k-1堆,由假設可知,不論先手怎樣取,後手總能取到最後一顆。下面我們分析一下後手最後取的石子數x的情況。

如果先手第一次取的石子數y>=f[k-1]/3,則這小堆所剩的石子數小於2y,即後手可以直接取完,此時x=f[k-1]-y,則x<=2/3*f[k-1]。

我們來比較一下2/3*f[k-1]與1/2*f[k]的大小。即4*f[k-1]與3*f[k]的大小,由數學歸納法不難得出,後者大。

所以我們得到,x<1/2*f[k]。

即後手取完k-1堆後,先手不能一下取完k堆,所以遊戲規則沒有改變,則由假設可知,對於k堆,後手仍能取到最後一顆,所以後手必勝。

即i=k+1時,結論依然成立。

對於不是FIB數,首先進行分解。

分解的時候,要取盡量大的Fibonacci數。

比如分解85:85在55和89之間,於是可以寫成85=55+30,然後繼續分解30,30在21和34之間,所以可以寫成30=21+9,

依此類推,最後分解成85=55+21+8+1。

則我們可以把n寫成 n = f[a1]+f[a2]+……+f[ap]。(a1>a2>……>ap)

我們令先手先取完f[ap],即最小的這一堆。由於各個f之間不連續,則a(p-1) > ap + 1,則有f[a(p-1)] > 2*f[ap]。即後手只能取f[a(p-1)]這一堆,且不能一次取完。

此時後手相當於面臨這個子遊戲(只有f[a(p-1)]這一堆石子,且後手先取)的必敗態,即先手一定可以取到這一堆的最後一顆石子。

同理可知,對於以後的每一堆,先手都可以取到這一堆的最後一顆石子,從而獲得遊戲的勝利。

若為最後取光者敗,同巴什博弈一樣即最後取光n-1個者勝。

例題:HDU 2516 -- 取石子遊戲 (裸題)

技術分享圖片
 1 #include <iostream>
 2 using namespace std;
 3 
 4 int main() {
 5     int fib[50], n; fib[0] = 1, fib[1] = 2;
 6     for (int i = 2; i < 45; i++) fib[i] = fib[i-1] + fib[i-2];
 7     while (cin >> n && n) {
 8         bool flag = false;
 9         for (int i = 0; i < 45; i++) if (fib[i] == n) flag = true;
10         if (flag) cout << "Second win" << endl;
11         else cout << "First win" << endl;
12     }
13     return 0;
14 }
View Code

總結(一般博弈的求解): 1. 狀態的表示; 2. 尋找必敗態; 3. 必敗態:面對該局面無論進行何種決策都會導致最終失敗; 4. 其他局面都成為必勝態(進行適當決策可以變成必敗態)。

博弈知識整理