数学—博弈

1.总述

 P-position为必败态,N-position为必胜态 能到必败态的状态为必胜态,只能到必胜态的状态为必败态。

做题思路:①手动或机器打表找规律 ②打SG表(ICG类型)③记忆化搜索(状态数有限)

注意:不要抱有记忆化搜索会少搜一些状态的侥幸心理

2.巴什博弈

问题模型:只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个,最后取光者得胜。

模型归纳:一堆n个物品,每次取一堆中的有限个,游戏次数有限。

思路:设出最后的状态为N态或P态,由N、P态之间转换关系顺推或倒推,最后求出所求状态。

例题:

1.悼念512汶川大地震遇难同胞——选拔志愿者 HDU - 2188

#include<bits/stdc++.h>

using namespace std;

/*

由题意可设n为P态,则n-1,n-2,...,n-m均为N态,n-m-1为P态

由此可观察出,当 n是m+1的倍数时,则可推出0是P态,否则为N态 

*/

int main()

{

    int Case;

    scanf("%d",&Case);

    for(int o=1;o<=Case;o++)

    {

        int n,m;

        scanf("%d%d",&n,&m);

        if(n%(m+1)==0)printf("Rabbit\n");

        else printf("Grass\n");    

    } 

    return 0;

}
View Code

3.威佐夫博弈

问题模型:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

模型归纳:两堆若干个物品,每次取一堆中的若干个或两堆中的相同个,游戏次数有限。

思路:奇异状态必败,否则必胜。

关于奇异状态:https://blog.csdn.net/qq_41311604/article/details/79980882

检验威佐夫博弈模型的一个小程序:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 //利用n^2的时间可以初始化出前n组奇异局势 
 4 int Mark[100005]={0};
 5 int main()
 6 {
 7     for(int i=1;i<=100;i++)
 8     {
 9         int tmp; 
10         for(int j=0;j<=i*2;j++)
11         {
12             if(!Mark[j])
13             {
14                 tmp=j;
15                 Mark[j]=1;
16                 break;
17             }
18         }
19         Mark[tmp+i-1]=1; 
20         printf("第%d个奇异局势:a为%d b为%d ",i,tmp,tmp+i-1);
21         printf("a的1.618倍为%.5lf 差值的1.618倍为%.5lf\n",(sqrt(5)+1)/2*tmp,(sqrt(5)+1)/2*(i-1)); 
22     }
23     return 0;
24 }
View Code

例题:

1.取石子游戏 HDU - 1527

 1 #include<bits/stdc++.h>
 2 
 3 using namespace std;
 4 
 5 //如果差值的1.618倍(取下整)等于a则为奇异局势
 6 
 7 //理解:我们已知奇异局势差值的1.618倍再取下整为a,设其为c,那么当a、b同时加减,c与a一定会产生差值
 8 
 9 //因此,非奇异局势一定不会有这种性质,即这个性质是充要的
10 
11 //同理,根据黄金分割的性质,a的1.618倍还等于b(但要取上整) 
12 
13 int main()
14 
15 {
16 
17     int a,b;
18 
19     while(scanf("%d%d",&a,&b)!=EOF)
20 
21     {
22 
23         if(a>b)swap(a,b);
24 
25         //利用差值的1.618倍: 
26 
27         //if(a==((int)((b-a)*(sqrt(5)+1)/2)))printf("0\n"); 
28 
29         //else printf("1\n");
30 
31         //利用a的1.618倍: 
32 
33         if(((int)((sqrt(5)+1)*a/2))+1==b)printf("0\n"); 
34 
35         else printf("1\n");
36 
37     }
38 
39     return 0;
40 
41 }
View Code

4.尼姆博弈

问题模型:有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。

模型归纳:若干堆若干个物品,每次取一堆中的若干个,游戏次数有限

思路:所有游戏状态的异或为0为必败态,否则为必胜态

证明(对总述中的三个命题):

  • 第一个命题显然,terminal position只有一个,就是全0,异或仍然是0。
  • 第二个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an<>0,一定存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。不妨设a1^a2^...^an=k,则一定存在某个ai,它的二进制表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。这时ai^k<ai一定成立。则我们可以将ai改变成ai'=ai^k,此时a1^a2^...^ai'^...^an=a1^a2^...^an^k=0。
  • 第三个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。因为异或运算满足消去率,由a1^a2^...^an=a1^a2^...^ai'^...^an可以得到ai=ai'。所以将ai改变成ai'不是一个合法的移动。证毕。 

例题:

1.Rabbit and Grass

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int main()
 4 {
 5     int m;
 6     while(1)
 7     {
 8         scanf("%d",&m);
 9         if(m==0)break;
10         int tmp1,tmp2=0; 
11         while(m--)
12         {
13             scanf("%d",&tmp1);
14             tmp2^=tmp1;
15         }
16         if(tmp2)puts("Rabbit Win!");
17         else puts("Grass Win!");
18     }
19     return 0;
20 }
View Code

 

5.Fibonacci博弈

问题模型:

有一堆个数为n的石子,游戏双方轮流取石子,满足: 

  • (1)先手不能在第一次把所有的石子取完; 
  • (2)之后每次可以取的石子数介于1到对手刚取的石子数的2倍之间(包含1和对手刚取的石子数的2倍)。 
  • 约定取走最后一个石子的人为赢家。

模型归纳:一堆若干个物品,每次取区间内的个数,游戏次数有限

思路:当n为Fibonacci数时,先手必败。即存在先手的必败态当且仅当石头个数为Fibonacci数。(打表找规律)

证明:https://blog.csdn.net/dgq8211/article/details/7602807

 

6.公平组合博弈(Impartial Combinatorial Games - ICG)

问题模型:

  • 定义:
  • (1)两人参与。
  • (2)游戏局面的状态集合是有限。
  • (3)对于同一个局面,两个游戏者的可操作集合完全相同
  • (4)游戏者轮流进行游戏。
  • (5)当无法进行操作时游戏结束,此时不能进行操作的一方算输。
  • (6)无论游戏如何进行,总可以在有限步数之内结束。

模型归纳:若干个游戏若干种状态,每次改变一个游戏的状态,游戏次数有限

思路:

  1. 将游戏对应一个有向无环图
  2. mex运算:取出不存在于目标集合中的最小非负整数
  3. Sprague-Garundy函数g:g(x)=mex{ g(y) | y是x的后继 }
  4. 将SG值看成Nim中的石子堆,则ICG的必胜策略对应Nim的必胜策略

得到sg函数的模板:

 1 void Getsg(int n)//n±íʾ×ÓÓÎÏ·"ʯ×Ó"¸öÊýÉÏÏÞ 
 2 {
 3     memset(sg,0,sizeof(sg));
 4     for(int i=1;i<=n;i++)
 5     {
 6         memset(Mark,0,sizeof(Mark));
 7         for(int j=1;f[j]<=i;j++)Mark[sg[i-f[j]]]=1;//f[i]ΪµÚiÖÖÄÜ×ߵIJ½Êý 
 8         for(int j=0;j<=n;j++)//ÇóÄܵ½´ïµÄ״̬ÖеÄ×îС·Ç¸ºÕûÊý̬
 9         {
10             if(!Mark[j])
11             {
12                 sg[i]=j;
13                 break;
14             }
15         }
16     }
17 } 
View Code

例题:

1.Fibonacci again and again

因为是公平组合游戏,且要求的只是判断状态,所以直接打sg表就好

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int f[20],Mark[1005],sg[1005]={0};
 4 void Init()
 5 {
 6     f[0]=1;f[1]=1;
 7     for(int i=2;i<=16;i++)f[i]=f[i-1]+f[i-2];//µÃµ½ì³²¨ÄÇÆõÊýÁв»´óÓÚ1000µÄÏî 
 8     
 9     for(int i=1;i<=1000;i++)//µÃµ½sgº¯Êý 
10     {
11         memset(Mark,0,sizeof(Mark));
12         for(int j=1;f[j]<=i;j++)Mark[sg[i-f[j]]]=1;
13         for(int j=0;j<=1000;j++)//ÇóÄܵ½´ïµÄ״̬ÖеÄ×îС·Ç¸ºÕûÊý̬
14         {
15             if(!Mark[j])
16             {
17                 sg[i]=j;
18                 break;
19             }
20         } 
21     }
22 }
23 void Solve()
24 {
25     int n,m,p;
26     while(cin>>n>>m>>p)
27     {
28         if(n==0&&m==0&&p==0)break;
29         if(sg[n]^sg[m]^sg[p])puts("Fibo");
30         else puts("Nacci");
31     }
32 }
33 int main()
34 {
35     Init();
36     Solve();
37     return 0;
38 }
View Code

7.反尼姆博弈,反公平组合博弈

问题模型:全为0为胜态,其余与之前内容相同

模型归纳:

  • 先手必胜当且仅当:
  • ( 1)所有堆的石子数都为 1 且游戏的 SG 值为 0;
  • ( 2)有些堆的石子数大于 1 且游戏的 SG 值不为 0。
  • 或者
  • (1)游戏的 SG 函数不为 0 且游戏中某个单一游戏的 SG 函数大于 1;
  • (2)游戏的 SG 函数为 0 且游戏中没有单一游戏的 SG 函数大于 1。

题目

1.kiki's game HDU - 2147

手动画表找规律:

 1 #include<bits/stdc++.h>
 2 /*
 3 思路:画图找规律,发现行数和列数均为奇数的话必败 
 4 */
 5 int main()
 6 {
 7     int n,m;
 8     while(1)
 9     {
10         scanf("%d%d",&n,&m);
11         if(n==0&&m==0)break;
12         if(n&1 && m&1)puts("What a pity!");
13         else puts("Wonderful!");
14     } 
15     return 0;
16 }
View Code

2.Good Luck in CET-4 Everybody! HDU - 1847

a.记忆化搜索:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 /*
 4 思路:根据博弈原理递推即可,此代码用记忆化搜索实现
 5 注意:1.用什么分别表示没有访问过、负态、胜态 
 6 时间复杂度:O(1000)空间复杂度:O(1000) 
 7 */
 8 int f[1005]={0};
 9 int dfs(int n)
10 {
11     if(f[n])return f[n];
12     if(n==0)return f[n]=-1;
13     
14     for(int i=0;n-(1<<i)>=0;i++)
15     {
16         if(dfs(n-(1<<i))==-1)return f[n]=1;
17     }
18     return f[n]=-1;
19 }
20 int main()
21 {
22     int n;
23     while(scanf("%d",&n)!=EOF)
24     {
25         if(dfs(n)==1)puts("Kiki");
26         else puts("Cici");
27     } 
28     return 0;
29 }
View Code

 b.转化为二进制(错误的做法,因为4既可以看作100也可以看作10+10)

3.Being a Good Boy in Spring Festival

比较新的一道题,求的是一般的nim游戏,先手必胜的第一步走法的方案数

考察的是对nim博弈模型的理解

考虑由必胜态到必败态的转移时,我们是找一个数变小,然后让异或和为0,为此只需要先求出原来的异或和,找到异或和的最高位是哪一位,那么有这一位的子游戏就是能够作为必胜到必败转移策略的子游戏(且只有这种情况),证明:

  1. 有这一位的话可以把这一位取反,然后较小的位也对应取反,这样可以使新异或和为0并且这样得到的新子游戏是较小的
  2. 若没有这一位的话,为了消掉异或和中的这一位,就必须向高位借位,这样会使异或和产生新的高位,证毕
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int main()
 4 {
 5     int n,Xor,ans,pos,a[105]={0};
 6 Ret:
 7     Xor=0,ans=0,pos=-1;
 8     scanf("%d",&n);
 9     if(n==0)goto End;
10     for(int i=1;i<=n;i++)scanf("%d",&a[i]),Xor^=a[i];
11     for(int i=20;i>=0;i--)if((1<<i)&Xor){pos=i;break;}
12     if(pos!=-1)
13     for(int i=1;i<=n;i++)if(a[i]&(1<<pos))ans++;
14     cout<<ans<<endl;
15     goto Ret;
16 End:
17     return 0;
18 }
19 /*
20 1   
21 10
22 11          3
23 100
24 101         5
25 110
26 111         7
27 1000
28 1001        9
29 1010
30 1011        11
31 */
View Code

4.Digital Deletions

不属于归纳出的任何一种模型,且状态数较少(100w),因此考虑用记忆化搜索

5.萌萌的糖果博弈

 

 

 

posted @ 2018-09-23 15:17  唉,队友呢?队友呢?队友呢?!!!  阅读(334)  评论(0编辑  收藏  举报