博弈论题目总结(一)——简单组合游戏
人类的本质是什么呢?复读机?鸽子?
博弈问题是很有意思的一类题目
我讲的可能不是很明白,但题目都不难建议自己思考
组合游戏的特点:
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 }