1. 程式人生 > >博弈論題目總結(一)——組合遊戲

博弈論題目總結(一)——組合遊戲

復讀 滿足 har 坐標 getc 當前 ++ 維護 人類

人類的本質是什麽呢?復讀機?鴿子?

博弈問題是很有意思的一類題目

我講的可能不是很明白,但題目都不難建議自己思考

組合遊戲的特點:

1.兩個人博弈,輪流做出最優決策

2.玩家在每個時刻做出的決策都是能預測到的,是一個確定的集合

3.每種狀態可能有多種方式到達,但同一種狀態不能在一次遊戲中重復到達,且沒有平局的情況

4.只要能進行決策,就一定要決策,不能跳過這個回合

SG組合遊戲

我們把每種狀態抽象成一個點,在起點有一顆棋子,兩個人選取最優策略輪流對這顆棋子進行移動,最後不能移動棋子的人失敗

顯然這張圖是一個有向無環圖

(以下用集合的名稱代指集合內的點)

有向圖的核

給定一張DAG圖<V,E>,如果V的一個點集S滿足:

1.S是獨立集

2.集合V-S中的點可以通過一步到達集合S中的點

那麽S是圖V的一個核

結論:核內節點對應SG組合遊戲的必敗態

Alice把棋子從S移動到V-S

然後Bob又通過一步把棋子從V-S移動到了S

Alice像是被支配了一樣,被迫把棋子移動到了沒有出度的必敗節點,Bob勝利

說多了也沒什麽用,還是看題吧

默認Alice先手,Bob後手

註意,本文討論的先手後手是針對狀態而言的,而不是整局遊戲

POJ 2368 Buttons (巴什博弈)

題面:

兩個人玩遊戲,有n個石子,兩個人輪流取,每次取[1,L]個石子,取走最後一個石子的人勝利。假設兩人禿頂聰明,問L為何值時第二個人必勝,輸出L的最小值

題解:

如果(L+1)是n的約數,那麽Bob必勝

Alice取了x個石子,Bob取L+1-x個石子..

發現按照這個策略取下去,Bob就贏了

Bob總能使這一輪石子的數量減少t+1個

技術分享圖片
 1 #include <cmath>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 #define N1 100010
 6 #define ll long long 
 7 #define ull unsigned long long 
 8
using namespace std; 9 10 const int inf=0x3f3f3f3f; 11 int n; 12 int gint() 13 { 14 int ret=0,fh=1;char c=getchar(); 15 while(c<0||c>9){if(c==-)fh=-1;c=getchar();} 16 while(c>=0&&c<=9){ret=ret*10+c-0;c=getchar();} 17 return ret*fh; 18 } 19 20 int main() 21 { 22 scanf("%d",&n); 23 int i,j,sq=(int)sqrt(n),ans=inf; 24 if(n%2==0&&n/2>=3) ans=min(ans,n/2); 25 for(i=3;i<=sq;i++) 26 { 27 if(n%i==0) 28 { 29 ans=min(ans,i); 30 if(n/i>=3) ans=min(ans,n/i); 31 } 32 } 33 if(n>=3) ans=min(ans,n); 34 printf("%d\n",ans-1); 35 return 0; 36 }
View Code

POJ 1063 取石子遊戲 (威佐夫博弈)

題面:

兩堆石子,兩個人輪流取石子,可以在一堆取任意數量或者在兩堆取相同數量,取走最後一個石子的人勝利。問最後誰贏了

題解:

把問題搞到二維坐標系上,$x,y$分別對應兩堆的石子個數

顯然$(0,0)$先手必敗,把先手必敗節點稱為奇異節點

發現奇異節點上下左右,以及右上和右下的點都不是奇異節點

如果$Alice$不在奇異節點上,那麽$Alice$可以通過一步操作到達奇異節點,然後$Bob$失敗

$(1,2)(3,5)$等等也是奇異節點

經過奇異節點的3條直線上的點,都能通過一步到達奇異節點

這不正是有向圖的核的模型麽

怎麽求答案呢

需要用到$Beatty$定理

$\frac{1}{a}+\frac{1}{b}=1$

定義數列$A$:$\left \lfloor an \right \rfloor$,數列$B$:$\left \lfloor bn \right \rfloor$

那麽$A,B$就是整數的劃分..證明不會

打表發現$B_{i}-A_{i}=i$

可得$b=a+1$,帶入解得$a=\frac{\sqrt{5}+1}{2},b=\frac{3-\sqrt{5}}{2}$

驗證一下就行了

技術分享圖片
 1 #include <cmath>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 #define N1 100010
 6 #define ll long long 
 7 #define ull unsigned long long 
 8 using namespace std;
 9 
10 const int inf=0x3f3f3f3f;
11 int n,m;
12 
13 int main()
14 {
15     int x,y,z;
16     while(scanf("%d%d",&n,&m)!=EOF)
17     {
18         if(n>m) swap(n,m);
19         if(n==0){ 
20             if(m==0) puts("0"); else puts("1");
21             continue; 
22         }else if(n==1&&m==1){ puts("1"); continue; }
23         y=m-n;
24         x=(int)((sqrt(5.0)+1)/2*y);
25         if(x==n) puts("0");
26         else puts("1");
27     }
28     return 0;
29 }
View Code

POJ 1063 Euclid‘s Game (多階段博弈)

題面:略

題解:

把問題轉化成我們熟悉的模型,相當於有一排石子堆,必須把前面的石子堆取完了才能取後面的,取最後一個石子的人贏,問誰贏

發現這次的遊戲是分階段進行的

假設現在面對的石子堆中有x個石子

如果$x=1$,必須取走這個石子,勝敗由下一堆石子決定

$x\geq 1$,可以自由轉移到當前堆剩余棋子為1的狀態,或者全取走進入下一輪遊戲,顯然必勝

用位運算反著推一推就ok了

技術分享圖片
 1 #include <cmath>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 #define N1 100
 6 #define M1 (N1<<1)
 7 #define ll long long 
 8 #define dd double
 9 #define idx(X) (X-‘a‘)
10 using namespace std;
11 
12 int gint()
13 {
14     int ret=0,fh=1; char c=getchar();
15     while(c<0||c>9){if(c==-)fh=-1;c=getchar();}
16     while(c>=0&&c<=9){ret=ret*10+c-0;c=getchar();}
17     return ret*fh;
18 }
19 ll n,m; int d;
20 int v[N1],f[N1];
21 void gcd(ll x,ll y)
22 {
23     if(!y) return ;
24     if(y>x) swap(x,y);
25     v[++d]=x/y; gcd(y,x%y);
26 }
27 
28 int main()
29 {
30     int i,j;
31     while(scanf("%lld%lld",&n,&m))
32     {
33         if(n==0&&m==0) break;
34         d=0; gcd(n,m);
35         memset(f,0,sizeof(f)); f[d]=1;
36         for(i=d-1;i>=1;i--)
37             if(v[i]==1) f[i]=(f[i+1]^1);
38             else if(v[i]>1) f[i]=(f[i+1]|1);
39         if(f[1]) puts("Stan wins");
40         else puts("Ollie wins");
41     }
42     return 0;
43 }
View Code

POJ 1678 I Love this Game! (博弈DP+單調隊列)

題面:略

題解:

想了一個單調隊列的做法,常數十分優秀,由於內存原因目前只排到了poj的rank3

由於$a>0$,只能從小到大取。所以把$a_{i}$從大到小排序,模擬根據結果去推決策的過程

有點類似於一雙木棋那道題$max/min$博弈的方法

定義$dp[i][0/1]$表示Alice/Bob取了$a_{i}$時答案的$max/min$

由於是兩個絕頂聰明在博弈,所以轉移和其它的$DP$不同,我們要選取最劣決策..

$dp[i][0]=min(dp[j][1]+a_{i}),dp[i][1]=max(dp[j][0]-a_{i}) $

決策$j$的區間可以用單調隊列維護

細節比較多

技術分享圖片
 1 #include <cmath>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 #define N1 10010
 6 #define ll long long 
 7 #define ull unsigned long long 
 8 using namespace std;
 9 
10 const int inf=0x3f3f3f3f;
11 int gint()
12 {
13     int ret=0,fh=1;char c=getchar();
14     while(c<0||c>9){if(c==-)fh=-1;c=getchar();}
15     while(c>=0&&c<=9){ret=ret*10+c-0;c=getchar();}
16     return ret*fh;
17 }
18 
19 int n,m,T,A,B,de;
20 int q0[N1],q1[N1],hd0,hd1,tl0,tl1,a[N1],dp[N1][2];
21 int cmp(int x,int y){ return x>y; }
22 
23 int main()
24 {
25     scanf("%d",&T);
26     while(T--) {
27         
28     int i,j,ans=-inf;
29     scanf("%d%d%d",&n,&A,&B);
30     for(i=1;i<=n;i++) a[i]=gint();
31     sort(a+1,a+n+1,cmp);
32     memset(dp,0,sizeof(dp));
33     hd0=hd1=1, tl0=tl1=0;
34     //q0[++tl0]=0; q1[++tl1]=0;
35     for(i=1,j=1;i<=n;i++)
36     {
37         if(a[i]==330)
38             de=1;
39         for(; j<i && a[j]-a[i]>B ;j++);
40         for(; j<i && A<=a[j]-a[i] && a[j]-a[i]<=B ;j++)
41         {
42             while( hd0<=tl0 && dp[j][1]<=dp[q0[hd0]][1] ) tl0--;        
43             q0[++tl0]=j;
44             while( hd1<=tl1 && dp[j][0]>=dp[q1[hd1]][0] ) tl1--;
45             q1[++tl1]=j;
46         }
47         while( hd0<=tl0 && a[q0[hd0]]-a[i]>B ) hd0++;
48         while( hd1<=tl1 && a[q1[hd1]]-a[i]>B ) hd1++;
49         if(hd0<=tl0) dp[i][0]=dp[q0[hd0]][1]+a[i]; else dp[i][0]=a[i];
50         if(hd1<=tl1) dp[i][1]=dp[q1[hd1]][0]-a[i]; else dp[i][1]=-a[i];
51     }
52     for(i=1;i<=n;i++) if( A<=a[i] && a[i]<=B ) ans=max(ans,dp[i][0]);
53     //for(i=1;i<=n;i++) printf("%d:%d %d\n",a[i],dp[i][0],dp[i][1]);
54     if(ans==-inf) ans=0;
55     printf("%d\n",ans);
56         
57     }
58     return 0;
59 }
View Code

博弈論題目總結(一)——組合遊戲