很好玩的博弈(巴什博弈,斐波那契博弈,威佐夫博弈,尼姆博弈)
參考部落格:https://blog.csdn.net/weixin_45006220/article/details/107388815?utm_source=app
https://blog.csdn.net/ACM_cxlove/article/details/7835016?utm_source=app
https://blog.csdn.net/u013514928/article/details/69055286?utm_source=app
一、博弈的特點
1.博弈的物件是兩個人,雙方交替進行某種規定好的操作。
2.博弈的勝負只取決於情況本身,與選手無關
3.博弈的失敗往往是先手的選手無法進行合法操作
二、四種經典博弈
1、巴什博弈
題目描述: 有一堆石子一共有n個,兩人輪流進行; 每走一步可以取走1…m個石子,最先取光石子的一方為勝。
解題思路:對於m+1件物品,無論先手取多少件物品,後手都能取剩下的,將這m+1件物品取完。因此,如果n是m+1的倍數,那麼無論先手怎麼取,後手必贏;反之,如果n不是m+1的倍數,那麼先手先取n%(m+1)個,接下來無論後手取多少個,先手總能取完m+1,先手必贏。
例題:hdu 1846 http://acm.hdu.edu.cn/showproblem.php?pid=1846
巴什博弈#include <algorithm> #includehdu1846<iostream> #include <cstdio> #include <cstring> #include <cmath> #include <cstdlib> #include <iostream> #include <queue> #include <stack> #include <ctype.h> #include <map> using namespace std; int main() { int n, i, t, b,j,k,l,r,c; scanf("%d",&c); while(c--) { scanf("%d%d",&n,&m); if (n%(m+1)==0) printf("second\n"); else printf("first\n"); } return 0; }
2.斐波那契博弈
題目描述:有一堆個數為n的石子,遊戲雙方輪流取石子,滿足:先手不能在第一次把所有的石子取完;之後每次可以取的石子數介於1到對手剛取的石子數的2倍之間(包含1和對手剛取的石子數的2倍)。約定取走最後一個石子的人為贏家,求必敗態。
解題思路:此處用數學歸納法證明。
我們假設當n是斐波那契的數列中的某個數,先手必敗。
①當n=2時,先手只能取1個,那麼顯然先手必敗。
②假設當n<=k時成立,則當n=k+1時,f[n]=f[k]+f[k-1]。此時可以把石子看成f[k]和f[k-1]兩堆。
假設先手取了>=f[k-1]堆的石子,那麼後手可以取完f[k],因為f[k]<f[k-1]*2
假設先手取了>=1/3 f[k-1]堆的石子,那麼後手可以把 f[k-1]這一堆石子取完,剩下的問題就是:接下來先手取石子,能否把f[k]的石子全部取完,即後手取走的這 2/3 f[k-1]和 1/2 f[k]的大小,也就是4*f[k-1]和3 *f[k] 的大小,顯然,還是後者大,所以,接下來先手取石子,不能將石子取完。而現在的狀況時:剩下了k堆石子,先手先取,根據我們的數學歸納法的假設,是後手贏。
假設先手取了<=1/3 f[k-1] 堆的石子呢?那我們可以將f[k-1]分成 f[k-2] 和 f[k-3] 兩個更小的斐波那契數來完成,依次遞迴下去,依舊可以得出相同的結論。
例題:VJ https://vjudge.net/contest/380862#problem/I
#include <algorithm> #include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <cstdlib> #include <iostream> #include <queue> #include <stack> #include <ctype.h> #include <map> using namespace std; const long long maxn=2e5+10; long long f[maxn],tree[maxn],cp[maxn],ma[maxn],m; int s[maxn],p[maxn],v[maxn],flag,ans; char x[maxn]; int main() { int n, i, t, b,j,k,l,r,c; f[0] = f[1] = 1; while(cin>>n&&n) { flag=0; if (n==1) { printf("Second win\n"); flag=1; } for (int i = 2; f[i - 1] < n; i++) { f[i] = f[i - 1] + f[i - 2]; { if (f[i] == n) { printf("Second win\n"); flag=1; break; } } } if (!flag) printf("First win\n"); } return 0; }View Code
3.威佐夫博弈
題目描述:首先有兩堆石子,博弈雙方每次可以取一堆石子中的任意個,不能不取,或者取兩堆石子中的相同個。先取完者贏。
解題思路:首先分析幾種特殊的情況。
零當兩堆石子分別是(0,0)時,因為先手不能取了,所以先手輸。
①當兩堆石子分別是(1,2)時,先手取左1,後手取右2;先手取右2,後手取左1;先手取右1,後手兩堆各取一個,三種取法都是後手贏。
②當兩堆石子分別是(3,5)時,先手不能先將其中一堆取完,這樣必輸;
先手如果取左1,後手取右4,變成了(2,1)的狀態
先手如果取左2,後手取右3,變成了(1,2)的狀態
先手如果取右1,後手取(2,2),變成了(1,2)的狀態
先手如果取右2,後手取(3,3),直接取完
先手如果取右3,後手取左2,變成了(1,2)的狀態
先手如果取右4,後手取左1,變成了(2,1)的狀態
所以,無論如何還是後手贏。
做接下來的模擬可以得出
③當兩堆石子分別是(4,7)時,也是先手必輸。
④(6,10)
⑤(8,13)
⑥ (9,15)
⑦(11,18)
通過找規律發現,他們的差值是逐級遞增的,而他們的首位數數局面中沒有出現過的第一個值。
再找規律會發現
★ 差值*黃金分割比=第一個值(較小的值) (話說好神奇的~~~)
黃金分割比 1.618=(sqrt(5)+1)/ 2
所以對於一組(m,n) 先判斷 (n-m)*(sqrt(5)+1)/ 2 是否等於 min(m,n) :如果是,那麼後手必贏;如果不是,那麼先手必贏,因為先手的第一步可以把它變成這裡的(m,n)的形式。
例題:https://www.51nod.com/Challenge/Problem.html#problemId=1072
#include <algorithm> #include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <cstdlib> #include <iostream> #include <queue> #include <stack> #include <ctype.h> #include <map> using namespace std; const long long maxn=2e5+10; int f[maxn],tree[maxn],cp[maxn],ma[maxn],m; int s[maxn],p[maxn],v[maxn],flag,ans; char x[maxn]; int main() { int n, i, t, b,j,k,l,r,c; f[0] = f[1] = 1; cin>>t; while(t--) { cin>>n>>m; double q=1.0*(sqrt(5)+1)/2; c=q*abs(m-n); if (c==min(n,m)) printf("B\n"); else printf("A\n"); } return 0; }View Code
4.尼姆博弈
題目描述:有3堆石子,每堆石子數量未知,兩人輪流取,每次取某堆中不少於1個的物品,最後取完者勝。
解題思路:我們也是先模擬幾種特殊情況:
①當三堆石子分別是(0,0,0)時,那麼先手沒有東西可取,先手必敗。
②當三堆石子分別是(0,n,n)時,那麼無論先手取其中一堆的k個石子(k<=n),只要後手取另外一堆與之相等的石子數即可獲勝,因此也是先手必敗。
③當三堆石子分別是(1,2,3)時,無論先手怎麼取,後手都可以把它變成(0,n,n)的形式,因此還是先手必敗。
但是要找這個規律,恐怕是很難找,但是就是有人如此的牛牪犇,把這個問題和二進位制聯絡在一起,用異或運算解決它。
這裡簡單說一下異或(XOR)的運算:兩個輸入,相同為0,不同為1 。
那麼就拿(1,2,3)舉個例子。
1=01
2=10
+ 3=11
————
0=00(不進位)
對於(0,n,n)的運算結果也是0 ,也就是說,只要滿足(a XOR b XOR c = 0) ,那麼接下來先手的人必輸。
我們知道,兩個相同的數的異或和為0, 所以對於任意的三個數(a,b,c),假設(a<b<c),如果滿足c=a XOR b即可,因為這樣變成了 a XOR b XOR c=a XOR b XOR a XOR b = a XOR a XOR b XOR b = 0 ,因此只要對 c 進行 c-(a XOR b)的運算就可以了
當然,尼姆博弈也可以推廣到n維運算:有n堆石子,每堆石子數量未知,兩人輪流取,每次取某堆中不少於1個的物品,最後取完者勝。
原理也相同:若剛開始是奇異局勢,當先手破壞了這個奇異局勢,後手只需把它恢復成奇異局勢即可,那麼後手必贏;如果剛開始不是奇異局勢,先手把它變成奇異局勢,那麼後手無論怎麼破壞,只要下一步先手把它還原成奇異局勢即可。
例如(7,9,12,15)
2^3 | 2^2 | 2^1 | 2^0 | |
7 | 0 | 1 | 1 | 1 |
9 | 1 | 0 | 0 | 1 |
12 | 1 | 1 | 0 | 0 |
15 | 1 | 1 | 1 | 1 |
顯然這個不是一個奇異局勢,那麼先手第一步只要把它變成奇異局勢即可。
第一種:把12變成1
2^3 | 2^2 | 2^1 | 2^0 | |
7 | 0 | 1 | 1 | 1 |
9 | 1 | 0 | 0 | 1 |
1 | 0 | 0 | 0 | 1 |
15 | 1 | 1 | 1 | 1 |
第二種:把 9變成4
2^3 | 2^2 | 2^1 | 2^0 | |
7 | 0 | 1 | 1 | 1 |
4 | 0 | 1 | 0 | 0 |
12 | 1 | 1 | 0 | 0 |
15 | 1 | 1 | 1 | 1 |
等等······,歸根結底,還是看剛開始是否處於奇異局勢,如果不是,那麼先手第一步取到奇異局勢就必贏;如果是奇異局勢,那麼後手只要保持奇異局勢就必贏。
例題:https://vjudge.net/problem/POJ-2234
#include <algorithm> #include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <cstdlib> #include <iostream> #include <queue> #include <stack> #include <ctype.h> #include <map> using namespace std; const long long maxn=2e5+10; int f[maxn],tree[maxn],cp[maxn],ma[maxn],m; int s[maxn],p[maxn],v[maxn],flag,ans; char x[maxn]; int main() { int n, i, t, b,j,k,l,r,c; while(cin>>t) { ans=0; for (i=1;i<=t;i++) { cin>>k; ans^=k; } if (ans) printf("Yes\n"); else printf("No\n"); } return 0; }View Code