人類的本質是什么呢?復讀機?鴿子?
博弈問題是很有意思的一類題目
我講的可能不是很明白,但題目都不難建議自己思考
組合游戲的特點:
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 }
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 }
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 }
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 }