博弈論算法學習記錄
學習資料
巴什博奕(Bash Game)
博弈原題
最簡單的巴什博奕題大概就是搶三十了,兩個人分別報數,每人一次可以報1~4
個數,先報數到30
的人贏。
這個游戲是典型的后手必勝游戲。首先,當一個人報數到了25
,那么他必然會贏,因為不論下一個人報數多少,他都能湊足30
。既然25 -> 30
是這樣,那么20 -> 25
也是這樣,凡是報數到20
的人下一回合都能報數到25
。以此類推,我們發現,后手必然能夠報數到5
,然后是10
,15
,20
,25
。
這樣就得到了是后手必勝。
博弈原理
我們分析一下其中的原理,為什么是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)
$$
博弈應用
判斷先\后手輸贏
很好解決,直接判斷a
,b
就可以了。
求先手輸贏,輸出第一次取法
先考慮兩堆同時取,這樣差值是不變的。如果只能取一堆,那么枚舉差值即可。
尼姆博奕(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;
}