zoukankan      html  css  js  c++  java
  • 博弈论题目总结(一)——简单组合游戏

    人类的本质是什么呢?复读机?鸽子?

    博弈问题是很有意思的一类题目

    我讲的可能不是很明白,但题目都不难建议自己思考

    组合游戏的特点:

    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
    ",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 ight floor$,数列$B$:$left lfloor bn ight floor$

    那么$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$,必须取走这个石子,胜败由下一堆石子决定

    $xgeq 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
    ",a[i],dp[i][0],dp[i][1]);
    54     if(ans==-inf) ans=0;
    55     printf("%d
    ",ans);
    56         
    57     }
    58     return 0;
    59 }
    View Code
  • 相关阅读:
    Solution: Win 10 和 Ubuntu 16.04 LTS双系统, Win 10 不能从grub启动
    在Ubuntu上如何往fcitx里添加输入法
    LaTeX 笔记---Q&A
    Hong Kong Regional Online Preliminary 2016 C. Classrooms
    Codeforces 711E ZS and The Birthday Paradox
    poj 2342 anniversary party
    poj 1088 滑雪
    poj 2479 maximum sum
    poj 2481 cows
    poj 2352 stars
  • 原文地址:https://www.cnblogs.com/guapisolo/p/10389746.html
Copyright © 2011-2022 走看看