博弈論:是二人或多人在平等的對局中各自利用對方的策略變換自己的對抗策略,達到取勝目標的理論。博弈論是研究互動決策的理論。博弈可以分析自己與對手的利弊關係,從而確立自己在博弈中的優勢,因此有不少博弈理論,可以協助對弈者分析局勢,從而採取相應策略,最終達到取勝的目的。
博弈論分類:(摘自百度百科)
(一)巴什博奕(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)的倍數,就能最後獲勝。
這個遊戲還可以有一種變相的玩法:兩個人輪流報數,每次至少報一個,最多報十
個,誰能報到100者勝。
巴什博弈博弈論裡面最簡單的一種形式。以下題目利用巴什博弈可以輕鬆解決:
1.
http://acm.hdu.edu.cn/showproblem.php?pid=1846 (brave game)
2.
http://acm.hdu.edu.cn/showproblem.php?pid=2147 (kiki's game)
3.
http://acm.hdu.edu.cn/showproblem.php?pid=2149 (public sale)
4.
http://acm.hdu.edu.cn/showproblem.php?pid=2188 (選拔志願者)
下面介紹分析此類題目的通用方法:P/N分析:
P點: 即必敗點,某玩家位於此點,只要對方無失誤,則必敗;
N點: 即必勝點,某玩家位於此點,只要自己無失誤,則必勝。
三個定理:
定理:
一、 所有終結點都是必敗點P(上遊戲中,輪到誰拿牌,還剩0張牌的時候,此人就輸了,因為無牌可取);
二、所有一步能走到必敗點P的就是N點;
三、通過一步操作只能到N點的就是P點;
以上題目均可以通過P/N分析法來解決。
這幾個題目都非常的簡單。下面僅對2149做簡單分析:(P/N分析也可以)
首先明確使用巴什博弈有個前提(特點)每次拿的數量是從1-m的不間斷整數開始的。注意這一點很重要。無此條件巴什博弈不成立!
其實這個題目稍微動動腦就可以把它轉換為巴什博弈(特點明顯)。你就假設兩人拍賣的時候是從給定的最高價開始—誰先拍賣到0誰就就是勝者。需要注意的是我們對特殊情況的考慮。具體見代碼:
#include<cstdio> #include<algorithm> using namespace std; int main() { freopen("game.in","r",stdin); freopen("game.out","w",stdout); int sum,maxnum; while(scanf("%d%d",&sum,&maxnum)!=EOF) { if(sum<=maxnum) { for(int i=sum;i<=maxnum;i++) printf(i==sum?"%d":" %d",i); printf("/n"); } else if(sum%(maxnum+1)!=0) { int result=sum%(maxnum+1); printf("%d/n",result); } else printf("none/n"); } return 0; }
(二)威佐夫博奕(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 。所以性質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 – ak)個物體,變為奇異局勢( ab – ak , ab – ak+ b – ak);如果a > ak ,b= ak + k,則從第一堆中拿走多餘的數量a – ak 即可;如果a
< ak ,b= ak + k,分兩種情況,第一種,a=aj (j < k),從第二堆裡面拿走 b – bj 即可;第二種,a=bj (j < k),從第二堆裡面拿走 b – aj 即可。
從如上性質可知,兩個人如果都採用正確操作,那麼面對非奇異局勢,先拿者必勝;反之,則後拿者取勝。
那麼任給一個局勢(a,b),怎樣判斷它是不是奇異局勢呢?我們有如下公式:
ak =[k(1+√5)/2],bk= ak + k (k=0,1,2,…,n 方括弧表示取整函數)
奇妙的是其中出現了黃金分割數(1+√5)/2 = 1。618…,因此,由ak,bk組成的矩形近似為黃金矩形,由於2/(1+√5)=(√5-1)/2,可以先求出j=[a(√5-1)/2],若a=[(1+√5)/2],那麼a = aj,bj = aj + j,若不等於,那麼a = aj+1,bj+1 = aj+1+ j + 1,若都不是,那麼就不是奇異局勢。然後再按照上述法則進行,一定會遇到奇異局勢。
(三)尼姆博奕(Nimm Game):有三堆各若干個物品,兩個人輪流從某一堆取任意多的
物品,規定每次至少取一個,多者不限,最後取光者得勝。
這種情況最有意思,它與二進位有密切關係,我們用(a,b,c)表示某種局勢,首先(0,0,0)顯然是奇異局勢,無論誰面對奇異局勢,都必然失敗。第二種奇異局勢是(0,n,n),只要與對手拿走一樣多的物品,最後都將導致(0,0,0)。仔細分析一下,(1,2,3)也是奇異局勢,無論對手如何拿,接下來都可以變為(0,n,n)的情形。
電腦演算法裡面有一種叫做按位模2加,也叫做異或的運算,我們用符號(+)表示這種運算。這種運算和一般加法不同的一點是1+1=0。先看(1,2,3)的按位模2加的結果:
1 =二進位01
2 =二進位10
3 =二進位11 (+)
———————
0 =二進位00 (注意不進位)
對於奇異局勢(0,n,n)也一樣,結果也是0。
任何奇異局勢(a,b,c)都有a(+)b(+)c =0。
如果我們面對的是一個非奇異局勢(a,b,c),要如何變為奇異局勢呢?假設 a < b< c,我們只要將 c 變為 a(+)b,即可,因為有如下的運算結果: a(+)b(+)(a(+)b)=(a(+)a)(+)(b(+)b)=0(+)0=0。要將c 變為a(+)b,只要從 c中減去 c-(a(+)b)即可。
例1。(14,21,39),14(+)21=27,39-27=12,所以從39中拿走12個物體即可達到奇異局勢(14,21,27)。
例2。(55,81,121),55(+)81=102,121-102=19,所以從121中拿走19個物品就形成了奇異局勢(55,81,102)。
例3。(29,45,58),29(+)45=48,58-48=10,從58中拿走10個,變為(29,45,48)。
下面先是一道簡單的尼姆博弈題目
http://acm.hdu.edu.cn/showproblem.php?pid=1850
只要運用上面的知識即可解決(具體細節見代碼)
//運用了重要性質:a^a=0 #include<cstdio> #include<algorithm> using namespace std; #define N 100+10 int main() { freopen("game.in","r",stdin); freopen("game.out","w",stdout); int heapnum,heap[N]; while(scanf("%d",&heapnum)!=EOF && heapnum) { int sum=0,ans=0; for(int i=1;i<=heapnum;i++) { scanf("%d",&heap[i]); sum^=heap[i]; } for(int i=1;i<=heapnum;i++) { if(heap[i]>(sum^heap[i])) ans++;//大於符號的優先順序要高於異或運算! } printf("%d/n",ans); } return 0; }
然而這並不是博弈的重點,LCY老師講到博弈之王道乃是SG值(具體見 http://acm.hdu.edu.cn/forum/read.php?tid=6875)。
SG值:一個點的SG值就是一個不等於它的後繼點的SG的且大於等於零的最小整數。
我的理解:在步驟允許的情況下,與前面一個必敗點的差(也就是說這個差是規定的、能走的、其中一個步數)!
後繼點:也就是按照題目要求的走法(比如取石子可以取的數量,方法)能夠走一步達到的那個點。(sg值的理解很抽象。我的隊友mo、xi說多畫畫就可以了)
現在我們拿http://acm.hdu.edu.cn/showproblem.php?pid=1847這道題分析一下。
我們枚舉下牌數為2-10的sg值:(SG(x)=mex{SG(x-S[i])}。)
num: 2 3 4 5 6 7 8 9 10
sg值: 2 0 1 2 0 1 2 0 1
具體的代碼如下:(當然這題用P/N分析要簡單得多,這裡僅理解sg值)
#include<cstdio> #include<algorithm> using namespace std; #define N 1000+10 int arr[11],sg[N]; int pre()//把1000以內的所有的可能一次拿的牌都算出來! { arr[0]=1; for(int i=1;i<=10;i++) arr[i]=arr[i-1]*2; return 0; } int mex(int x)//這是求解改點的sg值的演算法函數(採用記憶化搜尋) { if(sg[x]!=-1) return sg[x]; bool vis[N]; memset(vis,false,sizeof(vis)); for(int i=0;i<10;i++) { int temp=x-arr[i]; if(temp<0) break; sg[temp]=mex(temp); vis[sg[temp]]=true; } for(int i=0;;i++) { if(!vis[i]) { sg[x]=i; break; } } return sg[x]; } int main() { freopen("game.in","r",stdin); freopen("game.out","w",stdout); int num; pre(); while(scanf("%d",&num)!=EOF) { memset(sg,-1,sizeof(sg)); if(mex(num)) printf("Kiki/n"); else printf("Cici/n"); } return 0; }
(四)最後我們來看組合博弈:(博弈的精華)
博弈-取石子遊戲
http://acm.hdu.edu.cn/forum/read.php?fid=20&tid=5748
http://hi.baidu.com/netnode/blog/item/30932c2edc7384514fc226ea.html
組合博弈無疑是對sg值的熟練操作例如:有n堆石子,每次可以從第1堆石子裡取1顆、2顆或3顆,可以從第2堆石子裡取奇數顆,可以從第3堆及以後石子裡取任意顆…… 我們可以把它看作3個子遊戲,第1個子遊戲只有一堆石子,每次可以取1、2、3顆,很容易看出x顆石子的局面的SG值是x%4。第2個子遊戲也是只有一堆 石子,每次可以取奇數顆,經過簡單的畫圖可以知道這個遊戲有x顆石子時的SG值是x%2。第3個遊戲有n-2堆石子,就是一個Nim遊戲。對於原遊戲的每 個局面,把三個子遊戲的SG值異或一下就得到了整個遊戲的SG值,然後就可以根據這個SG值判斷是否有必勝策略以及做出決策了。其實看作3個子遊戲還是保
守了些,乾脆看作n個子遊戲,其中第1、2個子遊戲如上所述,第3個及以後的子遊戲都是“1堆石子,每次取幾顆都可以”,稱為“任取石子遊戲”,這個超簡單的遊戲有x顆石子的SG值顯然就是x。