关于博弈基础知识的总结:巴什博弈(Bash Game)、威佐夫博奕(Wythoff Game)、尼姆博奕(Nim Game)

来源:互联网 发布:网络征婚成功率 编辑:程序博客网 时间:2024/06/06 13:18

转载自http://www.aiuxian.com/article/p-942548.html



博弈论是二人或多人在平等的对局中各自利用对方的策略变换自己的对抗策略,达到取胜目标的理论。

基础的基础

a) 当前执行者想赢。这个是必要的,有时候题目中判别胜负的条件会与平时练习的恰好相反,此时你就应该按照题目要求思考,即在经典模型中思考当前执行者想输的策略。

b) 定义P-position和N-position,其中P代表Previous,N代表Next。直观的说,上一次move的人有必胜策略的局面是P-position,也就是“后手可保证必胜”或者“先手必败”,现在轮到move的人有必胜策略的局面是N-position,也就是“先手可保证必胜”。更严谨的定义是:1.无法进行任何移动的局面(也就是terminal position)是P-position;2.可以移动到P-position的局面是N-position;3.所有移动都导致N-position的局面是P-position我们在下文中称之为必胜点和必败点

相关题目;nyoj-518-取球问题 http://acm.nyist.net/JudgeOnline/problem.php?pid=518

该题是有“固定的某几个取法”例如:每次可以取1,3,7或8 


 

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. import java.util.Scanner;  
  2.   
  3. public class nyoj_518_取球游戏_一 {  
  4. //取球游戏 Accepted  70  760 java 08-24 10:57:44   
  5.     public static void main(String[] args) {  
  6.         Scanner sc = new Scanner(System.in);  
  7.         int T = sc.nextInt();  
  8.         int [] a = {1,3,7,8};  
  9.         while(T-->0){  
  10.             int N = sc.nextInt();  
  11.             boolean [] b = new boolean [N+10];  
  12.             for(int i=1; i<N; i++)  
  13.                 if(!b[i]){  
  14.                    for(int j=0; j<4; j++ )  
  15.                         b[i + a[j]] = true;                                       
  16.                 }         
  17.         System.out.println(b[N]?1:0);     
  18.     }  
  19.    }  
  20. }  


 

三个经典模型

巴什博弈(Bash Game

定义:只有一堆石子,两人轮流取,最少取一个,最多取m个,最后去完者为胜。

思考:

①当石子个数n=0时为必败点;(根据题意得来)

②当石子个数0<n<=m时为必胜点;(可以到达状态①)

③当石子个数n=m+1时为必败点;(只能到达状态②)

④当石子个数m+1<n<=(m+1)+m时为必胜点;(可以到达状态③)

⑤当石子个数n=(m+1)+m+1时为必败点;(只能到达状态④)

……

当n为m+1的整数倍时为必败点

把n写成这样的格式:n=r*(m+1)+s;

只要s不为0,即可胜。

相关题目:NYoj-23取石子(一):http://acm.nyist.net/JudgeOnline/problem.php?pid=23


[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1.    
  2. import java.util.Scanner;  
  3.   
  4. public class Main{  
  5.    
  6.     public static void main(String[] args) {  
  7.           
  8.         Scanner sc = new Scanner(System.in);  
  9.         int N = sc.nextInt();  
  10.         while(N-->0){  
  11.             int n = sc.nextInt();  
  12.             int m = sc.nextInt();                                       
  13.                   if(n%(m+1)==0)//前者输  
  14.                       System.out.println("Lose");  
  15.                   else  
  16.                       System.out.println("Win");  
  17.               }                           
  18.         }  
  19.     }          


相关题目:

2147 kiki's game 链接

 

分析:

有一个n*m的棋盘,在右上角(1,m)的位置放了一个硬币,A和B来移动这个硬币,移动的规则是:只能向左、向左下方、下方三个方向移动硬币,每次移动一步,最后一个不能再移动的人输。首先,我们看5*3的棋盘:他的哈密顿路径为6=(5-1)+(3-1):也就是如果只允许两个人向左或向下移动的步数为6,那么先移动的人必输。不过还可以向左下方移动,但这样移动的步数最多为min(n,m)-1,也就是n和m中较小的那个数减一步。所以5*3的棋盘最多能斜左下移动2步。这样移动的其实是一个直角边为min(n,m)-1的等边直角三角形。如图:

HDU 2147   kikis game - toints - toints的博客
同理:6*6的棋盘如图:
HDU 2147   kikis game - toints - toints的博客
 

 所以若果只允许两个人向左或向下移动的步数为奇数,或者向左下方移动的步数为奇数,那么先移动的人必赢。

//没懂。。。。??

再明白些:你所走的路径如果是奇数的话就前者必败,偶数时前者赢两人间隔着走,每次只可走一步!)。首先要知道向左走最多走m0=(m-1)格;向下走最多n0=(n-1)格;斜线走时,最多走min(n,m)上面的三角形是等腰的,等于走一格相当于同时n0-1 , m0-1 ,横着减少竖着减少一样多那就用n1=m1来表示走斜线时所消耗的横量、竖量。下图就是就有(n-n1)+n1=n,此时只要考虑n是奇数还是偶数,这是n > m时,反之,同样道理

这是其中的一种情况,还有n<=m的情况,在此就不说明了

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2. #include <stdio.h>  
  3.   
  4. using namespace std;  
  5.   
  6. int main()  
  7. {  
  8.     int n,m;  
  9.     while(scanf("%d%d",&n,&m),n||m)  
  10.     {  
  11.         int MIN = min(n,m);  
  12.         if(((n-1)+(m-1))&1 || (MIN-1)&1)//n,m都要减一  
  13.                 printf("Wonderful!\n");  
  14.         else printf("What a pity!\n");  
  15.         }  
  16.     return 0;  
  17. }  

其实上面的还可以改进些:就是直接考虑 n,m的奇偶性

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. #include <stdio.h>  
  2. int main()  
  3. {  
  4.     int n,m;  
  5.     while(scanf("%d%d",&n,&m)!=EOF && (n || m))  
  6.     {  
  7.         puts((n*m)&1?"What a pity!":"Wonderful!");  
  8.     }  
  9.     return 0;  
  10. }  



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

同样是巴什博弈这个经典模型,只不过原来是“取石子”,现在是“加石子”。

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. #include <stdio.h>  
  2. int main()  
  3. {  
  4.     int n,m,c;  
  5.     while(scanf("%d",&c)!=EOF)  
  6.     {  
  7.         while(c--)  
  8.         {  
  9.             scanf("%d%d",&m,&n);  
  10.             puts((m%(n+1))?"Grass":"Rabbit");  
  11.         }  
  12.     }  
  13.     return 0;  
  14. }  

1846 Brave Game

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. #include <stdio.h>  
  2. int main()  
  3. {//Accepted 1846 0MS 200K 191 B G++ 1983210400   
  4.   
  5.     int n,m,c;  
  6.     scanf("%d",&c);  
  7.     while(c--)  
  8.     {  
  9.         scanf("%d%d",&m,&n);  
  10.         puts((m%(n+1))?"first":"second");  
  11.     }  
  12.     return 0;  
  13. }  


 

hdu3863 No Gambling

这个题是各自操作自己的棋子,所以根据定义不算博弈,所以其结论皆不可用,这里可试着推导几个,你会发现先手必胜,这说明了两句俗语,那即是“先下手为强”,还有“棋输一步”。

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. #include <stdio.h>  
  2. int main()  
  3. {  
  4.     int n;  
  5.     while(scanf("%d",&n)!=EOF && n!=-1)  
  6.     {  
  7.         puts("I bet on Oregon Maple~");  
  8.     }  
  9.     return 0;  
  10. }  


 nyoj833取石子(七):

题意:一种变形,一堆石子摆成一圈,两个人轮流取,一人一次只取一个或者相邻两个,最后取到者为胜!

并非巴什博弈!还的推到,试探!

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. import java.util.Scanner;  
  2.   
  3. public class nyoj_811取石子_七 {  
  4.     public static void main(String[] args) {  
  5.           
  6.         Scanner sc =new Scanner(System.in);  
  7.           
  8.         while(sc.hasNext()){  
  9.             int n=sc.nextInt();  
  10.             if(n>2)            
  11.                 System.out.println("Yougth");                         
  12.             else  
  13.                      System.out.println("Hrdv");  
  14.         }  
  15.     }  
  16. }  


 

 

威佐夫博奕(Wythoff Game

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

思考:

把两堆石子的集合写为(a,b)不失一般性令我们只考虑a<=b的情况;

我们依次考虑(0,0)-(0,*)……(n,n)-(n,*)的情况。把所有状态看成一个二维数组,我们只需考虑上三角的情况;

(0,0)必败点,故(0,*)为必胜点;(1,1)一步可以取完,故也为必胜点;

(1,2)无论怎样取都只能到达必胜点,所以(1,2)为必败点;

(1,*)(2,*)可一步到达(1,2),故其都为必胜点;

此时我们发现一些规律,只有一些特殊的点是必败点,其余大多数情况为必胜点,再次我们不妨称必败点为“奇异局势”,这个定义我们在以后的学习中还可用到;

当(0,*)(1,*)(2,*)都考虑完后,开始考虑(3,*);(3,3)显然可一次取完为必胜点,(3,4)可以减(2,2)到达(1,2),所以也为必胜点;这里我们可以看到一个特点,如果a,b之差等于上述所提到“奇异局势”两堆石子之差时,则(a,b)-(m,m)是可以到达上述情况的,所以凡是后面要找到的点a,b,之差不能等于前面所找到的奇异局势;这时我们发现(3,5)为奇异局势;同理,(3,*)和(5,*)都为必胜点了,和上次推导一样(4,4)(4,5)(4,6)都为必胜点(4,7)为我们要找的奇异局势;(4,*)(7,*)必胜;(6,*)这一行还会有一个奇异局势,我们找到它是(6,10),相差为4;

此时,我们一经发现了一个规律,第i个奇异局势a,b之差为i,如果字典顺序寻找,则(a,b)中的a,为之前奇异局势中未出现的最小的自然数;写到这,你或许可以编程解决一些这样的问题了吧,但这貌似还是有点麻烦,没关系,数学家已经给出了我们a的通项公式(如要了解推导过程可自行查阅资料,这里不作为重点讨论),即ai=[i*(1+5)/2](方括表示下取整),bi=ai+i,。

相关题目:nyoj-161-取石子(四):http://acm.nyist.net/JudgeOnline/problem.php?pid=161

//java代码:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. import java.util.Scanner;  
  2.   
  3. public class Main{  
  4.     public static void main(String[] args) {  
  5.         Scanner input=new Scanner(System.in);  
  6.         while(input.hasNext()){  
  7.             int a=input.nextInt();  
  8.             int b=input.nextInt();  
  9.             if(a>b){  
  10.                 int t=a;  
  11.                 a=b;  
  12.                 b=t;  
  13.             }  
  14.             int k=b-a;  
  15.             if((int)((1+Math.sqrt(5))/2.0*k)==a)  
  16.                 System.out.println(0);  
  17.             else  
  18.                 System.out.println(1);  
  19.         }  
  20.     }  
  21. }          


 

尼姆博奕(Nim Game

定义:有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

思考:当只剩下一堆石子时,显然先手可以一次取完石子取得胜利,此为必胜点。当剩下两堆石子时,假如石子不相同,当前执行者一定可以取若干石子使得两堆相同(n,n),然后只要每次取石子数与对手相同(n-x,n-x),最后肯定会取完石子(0,0),所以(n,n)是必败点。当只有两堆石子时,站在对手的角度考虑,会发现当且仅当石子数相同才会是必败点。当三堆都有的时候情况有点复杂,(1,1,*)(2,2,*)……显然为必胜点,还有(1,2,3)是必败点,因为这个点只能到达前面所提到的必胜点。顺着这个思路,我们可以推出最小的必败点是(1,4,5)和(2,4,6),分析以上情况。假设有一个函数,设为SG(a1,a2,a3),这个函数的值就是这个点的胜负状态这里标记为1(必胜点)和0(必败点),假如这个点是必胜点,设SG(a1,a2,a3)=0,那么它肯定只能到达必胜点,也就是减少任何一堆石子数量的值,假设减少a3为a3',他的函数值SG(a1,a2,a3')应该一定为1;当SG(a1,a2,a3)=1时,一定存在某一堆减少数量假设也是改变a3',就可以使SG(a1,a2,a3')=0;还需满足一点就是SG(0,0,0)=0;根据上面的条件,我们或许想不出SG(a1,a2,a3)这个函数的表达式,当然数学家们如何想出的,我们不得而知,我们可以做的只有惊叹!接下来我们欣赏这个完美的结论吧,它已经推广到了N堆石子的情况。

对于一个Nim游戏的局面(a1,a2,...,an),它是P-position当且仅当a1^a2^...^an=0,其中^表示异或(xor)运算。神奇吧,很难想像怎么和异或扯上关系的,然而他的证明也并不复杂,在没有发现之前是很难想得出来的,这个的过程一定不缺乏深度思维的碰撞和灵感的火花。接下来我们就对他进行证明:

1、最后的状态,全为零,显然成立;

2、对于某个局面a1a2...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。 

3、对于某个局面a1a2...an,若a1^a2^...^an=0,一定不存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。因为异或运算满足消去率,由a1^a2^...^an=a1^a2^...^ai'^...^an可以得到ai=ai'。所以将ai改变成ai'不是一个合法的移动。证毕

通俗的说,如果异或的结果为0,那么改变任何一个数,显然结果肯定不能为0了,如果结果不为0,改变某个特定一个数,可以使结果等于0,而异或运算恰好就满足这个条件。因而成就了这个不可思议的定理。

相关题目:nyoj-585-取石子(六)

http://acm.nyist.net/JudgeOnline/problem.php?pid=585

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1.    
  2. #include<stdio.h>  
  3. int main(){  
  4.     int N;  
  5.     scanf("%d",&N);  
  6.     while(N--){  
  7.         int n;  
  8.         scanf("%d",&n);  
  9.         if(!n){  
  10.             printf("HRDV\n");  
  11.             continue;  
  12.         }  
  13.         int sum;  
  14.         scanf("%d",&sum);  
  15.         for(int i=1;i<n;i++){  
  16.             int a;  
  17.             scanf("%d",&a);  
  18.             sum^=a;  
  19.         }  
  20.         if(!sum)  
  21.             printf("HRDV\n");  
  22.         else  
  23.             printf("PIAOYI\n");  
  24.     }  
  25.     return 0;  
  26. }          

稍微变形的题目-hdu-2176:

取(m堆)石子游戏

Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1161    Accepted Submission(s): 695


Problem Description 
m堆石子,两人轮流取.只能在1堆中取.取完者胜.先取者负输出No.先取者胜输出Yes,然后输出怎样取子.例如5堆 5,7,8,9,10先取者胜,先取者第1次取时可以从有8个的那一堆取走7个剩下1个,也可以从有9个的中那一堆取走9个剩下0个,也可以从有10个的中那一堆取走7个剩下3个.
 
Input 
输入有多组.每组第1行是m,m<=200000. 后面m个非零正整数.m=0退出.
 
Output 
先取者负输出No.先取者胜输出Yes,然后输出先取者第1次取子的所有方法.如果从有a个石子的堆中取若干个后剩下b个后会胜就输出a b.参看Sample Output.
 
Sample Input 
245 4533 6 955 7 8 9 100
 
 
Sample Output

 

NoYes9 5Yes8 19 010 3
 
Author 
 
Zhousc
 
Source

 

ECJTU 2008 Summer Contest
 
Recommend 
lcy
 
 
[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. package 暑期培训_2013_08_23;  
  2. import java.util.Scanner;  
  3.   
  4. public class hdu_2176_取_2堆_石子游戏_博弈_201308231718 {  
  5.   
  6.     //Accepted  2176    781MS   5256K   569 B   Java    1983210400  
  7.     public static void main(String[] args) {  
  8.   
  9.         Scanner sc =new Scanner(System.in);  
  10.           
  11.         while(true){  
  12.               
  13.             int N =sc.nextInt();  
  14.             int [] a = new int[N+1];  
  15.             int [] f = new int[N+1];  
  16.             if(N==0)break;  
  17.             int t=0;  
  18.             for(int i=1; i<=N; i++){  
  19.                 a[i] = sc.nextInt();  
  20.                 t ^= a[i];  
  21.             }  
  22.             if(t==0){  
  23.                 System.out.println("No");  
  24.                 continue;  
  25.             }  
  26.             System.out.println("Yes");  
  27.             for(int i=1; i<=N; i++){  
  28.                  f[i]= t^a[i];  
  29.                  if(f[i]<=a[i])  
  30.                      System.out.println(a[i]+" "+f[i]);    
  31.             }  
  32.         }  
  33.   
  34.     }  
  35.   
  36. }  



 

参照资料:

http://www.math.ucla.edu/~tom/Game_Theory/Contents.html 

http://baike.baidu.com/view/1101962.htm 

http://hi.baidu.com/zhulei632/blog/item/657efefaf299b1dbb58f3152.html 


0 0