博弈知识整理

Posted _kangkang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了博弈知识整理相关的知识,希望对你有一定的参考价值。

博弈知识整理

一.  巴什博奕(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),b= 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 ^ a^ ...... ^ 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. 其他局面都成为必胜态(进行适当决策可以变成必败态)。

以上是关于博弈知识整理的主要内容,如果未能解决你的问题,请参考以下文章

JSP页面开发知识点整理

通过脚本片段绕过XSS防御

博弈论整理

IOS开发-OC学习-常用功能代码片段整理

VS2015 代码片段整理

读书笔记: 博弈论导论 - 07 - 完美信息的动态博弈 预备知识