博弈论 学习笔记

博弈论算法学习记录

学习资料

  • 总结
  • 巴什博奕
  • 威佐夫博弈
  • 尼姆博弈
  • 博弈问题与SG值

巴什博奕(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 
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 
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;
}

你可能感兴趣的:(博弈论 学习笔记)