博弈知识整理
一. 巴什博奕(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 }
二. 威佐夫博弈(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 }
三. 尼姆博弈(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:每个奇异局势一步后必为非奇异局势
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 }
四. 斐波那契博弈(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 }