向世界分享科學之美,讓科學流行起來
在書籍《編程之美》中,總共講述了三個關於取石子博弈的問題。書中對這三個博弈問題的本身都有詳細的解答。然而,看懂這些解答本身並不是一件難事。我們學知識呢,應該學會舉一反三,這樣纔算是真正掌握了知識,同時也才能真正體會到解決問題的樂趣。本文寫作的目的就是從這三個問題出發,然後對一系列與之相關的拓展問題,並給予相應的分析與論證。
與其他類型的問題不同的是,博弈論的問題只要稍做修改,其解答過程就很可能和原問題完全不一樣。絕不是問題修改一點,答案只需更改一點的。然而,就最本質的分析思路而言卻是想通的。話不多說,接下來就對這三個問題展開拓展性討論吧。
尼姆博弈
原題如下:有N堆石頭,記第i堆石頭數量爲。玩家A和B由A開始依次輪流取石頭,每次玩家從N堆石頭中選擇一堆石頭,取這一堆石頭的任意數目(至少一個)。最後取光石頭者獲勝。
答案:這題是經典的博弈題。結論非常有趣,將遊戲的局勢兩類,一類滿足將每堆石頭的數量取異或和等於0:;另一類滿足將每堆石頭的數量取異或和結果不等於0:。
其中,當且僅當局勢時,S爲奇異局勢。
拓展一:取子過程和原問題相同,不同的是最後取光石頭的人輸了。
對拓展一分析如下:
首先,我們發現沒有石頭的狀態爲非奇異局勢。而只有1塊石頭的狀態爲奇異局勢,因爲當玩家遇到這一狀態時,他只能取那唯一的一塊石頭,從而成爲最後取光石頭的人而輸掉遊戲。
由上分析,不難發現“局勢時,S爲奇異局勢”這一命題就不成立了。因爲只有1塊石頭,對應的異或值非0,但卻是奇異局勢,而沒有石頭時,異或值爲0,卻非奇異局勢。
我們將局勢再按照是否存在某一堆石頭多於一個這一條件將局勢再分爲以下兩種情況:1、任何一堆石頭的石頭數量均不大於1:;2、存在某一堆石頭數量多於1個:。
接下來,我們將分別對這兩種情況進行分析。
①局勢時:
這種情況下,比賽的結果就完全取決於石頭的堆數N。當玩家面對N爲奇數時,之後的每回合面對的石頭堆數均爲奇數。總是要面對只有1塊石頭的局面而輸。同理,玩家面對N爲偶數時,他永遠不會遇到只有1塊石頭的局面。
因此,如果局勢時,N爲奇數爲奇異局勢。
②局勢時:
這種情況比較複雜。我們先將局勢再細分爲如下兩種局勢集合:1、只有一堆石頭的數量大於1:;2、多於一堆石頭的數量大於1:。
當時,可以得出以下性質。
性質一:如果局勢,則S爲非奇異局勢。
證:將N分爲如下兩種情況進行討論。
情況1:N爲奇數,此時玩家取第i堆石頭,將這堆石頭取到只剩1個。此時,符合情況①,且剩下的石頭堆數爲奇數。可以將狀態轉爲奇異狀態。
情況2:N爲偶數,此時玩家取第i堆石頭,將這堆石頭全取完。剩下的石頭堆數爲N-1,而N-1爲奇數。此時,也符合情況①,且剩下的石頭堆數爲奇數。可以將狀態轉爲奇異狀態。
綜上所述,題設中的所有狀態都能夠通過某種方法使對手面臨奇異狀態。因此,題設中的狀態爲非奇異狀態。
接下來,當初始博弈局勢時,玩家在博弈過程中必然出現局勢。
證:顯然,當遊戲結束時只有0堆石頭滿足。
記第t輪取石頭時,局面中滿足的堆數爲。根據遊戲規則,每次玩家只能選取一堆石頭取,因此每次取石頭至多減少一堆數目大於1的石頭堆,即或。假設,遊戲過程中不存在只有一堆石頭滿足的情況。即。由於爲遞減序列,因此必存在t,有。與或相矛盾。
性質二:對於新的遊戲規則,當且僅當局勢時,S爲奇異局勢。
證:首先,當遊戲結束時,只剩下一堆石頭數量大於1,即結束局勢。不難證明,即。由性質一可知,該狀態爲非奇異狀態。
對於其他狀態,由最初的尼姆博弈的論證過程可知,當時,存在一種方法使得對手面臨的局面,而當時,則無論採用怎樣的取法,對手都將面臨的局面。
那麼,假如當前玩家A所面臨的局勢,那麼他無論怎麼取,對手都將面臨局勢。且對手(玩家B)都有辦法讓該玩家永遠面臨局勢。因此,玩家A不可能面臨局勢。因爲,假如玩家A面臨的局勢,則說明他面臨的局勢滿足,與“對手永遠讓他面臨局勢”相矛盾。而隨着遊戲的進行,滿足的石頭堆的數目只會不斷減少,最終必然出現局勢,而玩家A永遠不可能遇到這一局勢。因此,最終將由玩家B面臨該局勢而贏得遊戲。因此局勢爲奇異局勢。
反過來,如果當前玩家面臨局勢時,他就可以採取上述情況中對手的策略從而讓自己獲勝。因此局勢爲非奇異局勢。
性質二得證。
綜上所述,如果遊戲的初始局勢,那麼就局勢S中還剩多少堆石頭,當且僅當堆數爲奇數時是奇異局勢;如果初始局勢,則當且僅當時爲奇異局勢。
拓展二:假如玩家可以選擇的堆數爲1—k堆,那情況又該如何呢?
分析這一問題時,我們先來回顧一下只能取1堆時(原問題)的分析過程。原問題判斷博弈局勢是否是奇異局勢的最主要的判斷標準就是每一堆石頭數量的異或和是否等於0。
爲了將這一結果進行拓展,我們不妨換個角度來看待異或和這一問題。異或和的實質就是對每堆石頭數量對應的二進制數的每一位獨立相加,然後再對其模2。從這一角度上看,只能取1堆時是模2,那有沒有可能可以取到K堆時只要判斷模k+1,就可以判斷局勢是否是奇異局勢了呢?
帶着這一猜想,我們進行了進一步探究。
首先,在沒有石頭的時候與原問題一樣是奇異局勢。此時,模多少都是0。
爲了更好地進行表達,我們先將石頭的數量表示成二進制:設第i堆石頭的數量爲,則其對應的二進制表示爲。根據上述理論,可將異或和表示成爲。
記表示對的二進制表示的每位相加然後再模k的運算,是異或和運算的拓展,表示爲:。傳統的異或和相當於。
和之前的推論過程一樣,我們把遊戲局勢分爲兩類,一類滿足將每堆石頭數量的二進制表示每位相加模k+1等於0:;另一類滿足將每堆石頭數量的二進制表示每位相加模k+1的結果不等於0:。
接下來我們將論證以下性質成立:
性質三:當局勢時,存在一種方法使局勢變爲;而當局勢時,取完石頭後局勢都將變成。
證:首先,當局勢時,記集合表示將要取的石頭堆的集合。將該集合的二進制位每位相加然後模k+1可得:。由於要取的石頭堆不大於k,因此有。不難推斷,假如要保持取完後滿足局勢,則需要保持取完之後的值與原來完全一樣。
假設取之前,而取完之後有,且。而對求累加和,可以表示爲:。這說明取石頭前後中的石頭是總量是沒有變化的。與每次取石頭至少取一個這個前提矛盾。
因此,當局勢時,無論採用何種策略,局勢都將變成。
而當局勢時,則可以通過以下步驟使局勢。
令表示當前被選擇的石頭堆數的集合,而則表示已被選擇的石頭的堆數。同時,任何被選取的石頭堆維護性質①:,其中表示已經任意均已被處理完成,即。爲正無窮。
初始狀態下。顯然滿足性質①。
令,每次循環都尋找最大的j滿足。
然後,我們來考察值三種情況:
情況①::由於滿足性質①,此時,只要從中選擇個,即可令變爲0。然後令。該操作並不影響,而且更新後,依舊滿足性質①。
情況②::同樣由於性質①,可將所有中的,則更新後的。此時,不難論證在未被選擇的集合中,至少有堆石頭滿足。這幾堆取若干個石頭,使之滿足,進一步變成了0,且滿足性質①。最後將新選擇的堆石頭的編號加入,令。
情況③::這一情況同情況一相類似。與之唯一的不同是從中選擇令變爲k+1。
可見,以上三種情況,都是能夠從高到低將結果所有變爲0。因此,按照上述取法,必然可以獲得一個新的局勢。
由此,性質三得證。
從性質三不難進一步推斷:當前局勢S爲奇異局勢,當且僅當當。其論證過程和原題幾乎一樣,本文將不再贅述。
爲了更好地說明局勢時,可以通過上文的步驟使得。本文用一個簡單的例子加以說明:
假設當前局勢S={12,24,6,8,8},k=3。
將這些石頭數量轉成二進制如下:
12=<01100>
24=<11000>
6 =<00110>
8 =<01000>
8 =<01000>
進行運算得:R=<10210>
一開始爲空,R=<10210>,R中最大非0位爲第4位。符合上述情況②,取第2堆石頭24個石頭,取9個將其置爲<01111>。S={12,15,6,8,8},={2}。
R=<00320>,R中最大非0位爲第2位。取合上述情況②,從中每堆再取4個。取第1堆石頭12個石頭,取1個將其置爲<01011>;取第3堆石頭6個石頭,取3個將其置爲<00011>。S={11,11,3,8,8},={1,2,3}。
R=<00033>,R中最大非0位爲第1位。取合上述情況①,從中每堆再取2個。S={9,9,1,8,8}。
R=<00003>,R中最大非0位爲第0位。取合上述情況①,從中每堆再取1個。S={8,8,0,8,8}。
最終R=<00000>。即取第1,2,3堆,各取16,4,6個。
進一步,本人寫了其實現算法如下:
#define N 1000
#define BIT 30
//二進制相加然後模K+1
void getMod(int an[],int n,int k,int bn[]){
for (int i=0;i<n;i++)
{
if (an[i]<0)
{
bn[0]=-1;
cout<<"fuck an[i]<0"<<endl;
return ;
}
int j=0;
int t=an[i];
while (t)
{
if (t%2)
{
bn[j]++;
}
t/=2;
j++;
}
}
for (int j=0;j<=BIT;j++)
{
bn[j]=bn[j]%(k+1);
}
}
//判斷是否奇異
bool isQiyi(int an[],int n,int k){
int bn[32];
memset(bn,0,sizeof bn);
getMod(an,n,k,bn);
for (int j=0;j<=BIT;j++)
{
if (bn[j])
{
return false;
}
}
return true;
}
//判斷是否奇異
bool isQiyi(int bn[]){
for (int j=0;j<=BIT;j++)
{
cout<<bn[j]<<" ";
}
cout<<endl;
for (int j=0;j<32;j++)
{
if (bn[j])
{
return false;
}
}
return true;
}
//判斷是否已選
bool hasSel(int sel[],int n,int t){
for (int i=0;i<n;i++)
{
if (sel[i]==t)
{
return true;
}
}
return false;
}
//獲得取石子的結果
void getStone(int an[],int n,int k){
int rn[N];
int sel[N];
memset(rn,0,sizeof rn);
memset(sel,0,sizeof sel);
int bn[32];
memset(bn,0,sizeof bn);
getMod(an,n,k,bn);
int done=0;
while (!isQiyi(bn))
{
int t=-1;
for (int i=31;i>=0;i--)
{
if (bn[i])
{
t=i;
break;
}
}
if (bn[t]<=done)
{
for (int i=0;i<bn[t];i++)
{
rn[sel[i]]+=(1<<t);
}
bn[t]=0;
}else if (bn[t]<k+1)
{
for (int i=0;i<done;i++)
{
rn[sel[i]]+=(1<<t);
bn[t]--;
}
for (int i=0;i<bn[t];i++)
{
int theSel=-1;
for (int j=0;j<n;j++)
{
if (!hasSel(sel,done,j)&&(an[j]&(1<<t)))
{
theSel=j;
break;
}
}
rn[theSel]+=(an[theSel]&((1<<(1+t))-1))-((1<<(t))-1);
for (int j=0;j<t;j++)
{
if (!(an[theSel]&(1<<j)))
{
bn[j]++;
}
}
sel[done]=theSel;
done++;
}
bn[t]=0;
}else{
bn[t]%=(k+1);
for (int i=0;i<bn[t];i++)
{
rn[sel[i]]+=(1<<t);
}
bn[t]=0;
}
for (int i=0;i<done;i++)
{
printf("Select:%d\tgetNum:%d\n",sel[i],rn[sel[i]]);
}
cout<<endl;
}
for (int i=0;i<done;i++)
{
printf("Select:%d\tgetNum:%d\n",sel[i],rn[sel[i]]);
}
for (int i=0;i<n;i++)
{
rn[i]=an[i]-rn[i];
cout<<rn[i]<<" ";
}
cout<<endl;
printf("Res:%d",isQiyi(rn,n,k));
}