1. 程式人生 > 實用技巧 >很好玩的博弈(巴什博弈,斐波那契博弈,威佐夫博弈,尼姆博弈)

很好玩的博弈(巴什博弈,斐波那契博弈,威佐夫博弈,尼姆博弈)

參考部落格: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>
#include 
<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; }
hdu1846

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