首先,先拿一個例題介紹一下巴什博弈。(Bash Game)
最重要的公式n%(m+1)==0先手必敗否則後手必敗
http://acm.hdu.edu.cn/showproblem.php?pid=1846
像這樣2人採用最優策略取石子的問題就是典型的巴什博弈的思想
假設一開始有n個石子那麼n一定可以寫成n=(m+1)*r+s的形式,既然這樣的話,假如A先取,那麼一開始A取走s個石子(s<m),之後B取走k個石子,所以說,假如A再取走m+1-k個石子,之後就剩餘(m+1)*(r-1)個石子給B下一次取,也就是說如果不斷取下去,最終該B取的時候還有(m+1)*1個石子,B不論拿走幾個,A下一次都可以一次取完,所以說,當s=n%(m+1)中,s>0的時候,爲A的必勝態
if(n%(m+1)==0)
printf("second\n");
else
printf("first\n");
http://acm.hdu.edu.cn/showproblem.php?pid=2149(巴什博弈的推廣)
加價在(1-N)之間物品價值低價M只要有人加價大於等於M就贏了
分三種情況
1.M<=N,直接便利從M到N。
2.M%(N+1)==0後手獲勝
3.M%(N+1)>0先手可以初始拿從(1-N)獲勝
if(m<=n)
{
for(int k=m;k<=n;k++)
{
if(k==m)
printf("%d",k);
else
printf(" %d",k);
}
printf("\n");
continue;
}
if(m%(n+1)>=1)
{
int flag=0;
for(int k=1;k<=n;k++)
{
if((m-k)%(n+1)==0)
{
if(flag==0)
printf("%d",k);
else
printf(" %d",k);
flag=1;
}
}
printf("\n");
}
else
printf("none\n");
http://acm.hdu.edu.cn/showproblem.php?pid=1847
http://acm.hdu.edu.cn/showproblem.php?pid=2188
http://acm.hdu.edu.cn/showproblem.php?pid=2147
分析此類問題主要放法是:P/N分析:
P點:即必敗點,某玩家位於此點,只要對方無失誤,則必敗;
N點:即必勝點,某玩家位於此點,只要自己無失誤,則必勝。
三個定理:
一、所有終結點都是必敗點P(上游戲中,輪到誰拿牌,還剩0張牌的時候,此人 就輸了,因爲無牌可取);
二、所有一步能走到必敗點P的就是N點;
三、通過一步操作只能到N點的就是P點;
P-N圖中只要m,n分別是P-N圖中的行列(m%2==1&&n%2==1)
先手必敗,否則後手必敗。
威佐夫博弈(Wythoff Game)
有兩堆各若干的物品,兩人輪流從其中一堆取至少一件物品,至多不限,或從兩堆中同時取相同件物品,規定最後取完者勝利。
直接說結論了,若兩堆物品的初始值爲(x,y),且x<y,則另z=y-x;
記w=(int)[((sqrt(5)+1)/2)*z ];
若w=x,則先手必敗,否則先手必勝。
問題:首先有兩堆石子,博弈雙方每次可以取一堆石子中的任意個,不能不取,或者取兩堆石子中的相同個。先取完者贏。
分析:首先我們根據條件來分析博弈中的奇異局勢
第一個(0 , 0),先手輸,當遊戲某一方面對( 0 , 0)時,他沒有辦法取了,那麼肯定是先手在上一局取完了,那麼輸。
第二個 ( 1 , 2 ),先手輸,先手只有四種取法,
1)取 1 中的一個,那麼後手取第二堆中兩個。
2)取 2 中一個,那麼後手在兩堆中各取一個。
3)在 2 中取兩個,那麼後手在第一堆中取一個。
4)兩堆中各取一個,那麼後手在第二堆中取一個。
可以看出,不論先手怎麼取,後說總是能贏。所以先手必輸!
第三個 ( 3 , 5 ),先手必輸。首先先手必定不能把任意一堆取完,如果取完了很明顯後手取完另一堆先手必輸,那麼
假如看取一堆的情況,假設先手先在第一堆中取。 取 1 個,後手第二堆中取4個,變成(1 ,2)了,上面分析了是先手的必輸局。
取 2 個,後手第二堆中取3個,也變成( 1 , 2)局面了。
假設先手在第二堆中取,取 1 個,那麼後手在兩堆中各取 2 個,也變成 ( 1 , 2 )局面了。
取 2 個 ,那麼後手可以兩堆中都去三個, 變成 ( 0 , 0)局面,上面分析其必輸。
取 3 個,後手兩堆各取 1 個 ,變成( 1 , 2)局面了。
取 4 個,後手在第一堆中取一個,變成( 1 , 2)局面了。
可見不論先手怎麼取,其必輸!
第四個(4 , 7),先手必輸。
自己推理可以發現不論第一次先手如何取,那麼後手總是會變成前面分析過的先手的必輸局面。
那麼到底有什麼規律沒有呢,我們繼續往下寫。
第四個 ( 6 ,10 )
第五個 ( 8 ,13)
第六個 ( 9 , 15)
第七個 ( 11 ,18)
會發現他們的差值是遞增的,爲 0 , 1 , 2, 3, 4 , 5 , 6, 7.....n
而用數學方法分析發現局面中第一個值爲前面局面中沒有出現過的第一個值,比如第三個局面,前面出現了 0 1 2,那麼第三個局面的第一個值爲 3 ,比如第五個局面,前
面出現了 0 1 2 3 4 5 ,那麼第五個局面第一個值爲6。
再找規律的話我們會發現,第一個值 = 差值 * 1.618
而1.618 = (sqrt(5)+ 1) / 2 。
大家都知道0.618是黃金分割率。而威佐夫博弈正好是1.618,這就是博弈的奇妙之處!
下面來看看威佐夫博弈常見的三類問題:
1)給你一個局面,讓你求是先手輸贏。
(判斷是否是奇異局勢;方法(x,y)中x<y;若x==(sqrt(5)+ 1) / 2 *(y-z)則先手必敗)
有了上面的分析,那麼這個問題應該不難解決。首先求出差值,差值 * 1.618 == 最小值 的話後手贏,否則先手贏。(注意這裏的1.618最好是用上面式子計算出來的,否則精
度要求高的題目會錯)
2)給你一個局面,讓你求先手輸贏,假設先手贏的話輸出他第一次的取法。
首先討論在兩邊同時取的情況,很明顯兩邊同時取的話,不論怎樣取他的差值是不會變的,那麼我們可以根據差值計算出其中的小的值,然後加上差值就是大的一個值,當
然能取的條件是求出的最小的值不能大於其中小的一堆的石子數目。
加入在一堆中取的話,可以取任意一堆,那麼其差值也是不定的,但是我們可以枚舉差值,差值範圍是0 --- 大的石子數目,然後根據上面的理論判斷滿足條件的話就是一種合理的取法。
輸入所有的P點 http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=837
http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=161
模板題
第一種的解:
long long temp=max(a,b)-min(a,b);
long long temp1=(sqrt(5)+1)/2.0*temp;
第二種解的模板:例題:http://acm.hdu.edu.cn/showproblem.php?pid=2177
int vis[1000005];
long long wzf(long long x,long long y)
{
int t=max(x,y)-min(x,y);
long long temp=(sqrt(5)+1)/2.0*t;
if(temp==min(x,y))
return 0;
else
return 1;
}
void display(int x,int y)
{
if(vis[x]==0 || vis[y]==0)
{
vis[x]=1;
vis[y]=1;
if(x<=y)
printf("%d %d\n",x,y);
else
printf("%d %d\n",y,x);
}
}
int main()
{
long long a,b;
while(scanf("%lld%lld",&a,&b)!=EOF)
{
memset(vis,0,sizeof(vis));
if(a==0 && b==0)
break;
if(wzf(a,b)==0)
printf("0\n");
else
{
printf("1\n");
for(long long k=a,kk=b;kk>=0 && kk>=0;k--,kk--)
{
if(wzf(k,kk)==0)
display(k,kk);
}
long long t=max(a,b);
for(long long k=0;k<=t-1;k++)
{
if(wzf(min(a,k),max(a,k))==0)
{
display(min(a,k),max(a,k));
}
}
}
}
return 0;
}
尼姆博弈
尼姆博弈指的是這樣一個博弈遊戲:有任意堆物品,每堆物品的個數是任意的,雙方輪流從中取物品,每一次只能從一堆物品中取部分或全部物品,最少取一件,取到最後一件物品的人獲勝。
結論就是:把每堆物品數全部異或起來,如果得到的值爲0,那麼先手必敗,否則先手必勝。
模板如下:
#include <cstdio>
#include <cmath>
#include <iostream>
using namespace std;
int main()
{
int n,ans,temp;
while(cin>>n)
{
temp=0;
for(int i=0;i<n;i++)
{
cin>>ans;
temp^=ans;
}
if(temp==0) cout<<"後手必勝"<<endl;
else cout<<"先手必勝"<<endl;
}
return 0;
}