博弈论小结

博弈论小结

一、重头戏之尼姆博弈

  概述:有n堆若干个石子,两个人轮流从某一堆取任意多的石子,规定每次至少取一个,多者不限,最后取光者得胜。

  分析一下可知,如果n-2堆石子个数为0,剩下的两堆石子个数相同,那么面临这种局势的人必败。就比如说我有三堆石头个数分别为5 5 0,那么不论先手怎么操作,后手只需要在另一堆中取走同样多的石子就可以一直保证自己不败。5 5 0 --> 3 5 0 --> 3 3 0 --> 0 3 0 -->0 0 0 后手必胜。

  现在我们引入L . Bouton在1902年给出的定理:状态(x1,x2,x3...xn)为必败状态当且仅当x1^x2^x3……^xn=0。

  57(10) = 111001(2) ,即:57(10)=2^5+2^4+2^3+2^0。于是,我们可以认为每一堆物品数由2的幂数的子堆组成。这样,含有57枚物品大堆就能看成是分别由数量为32、16、8、1的各个子堆组成。 

  现在考虑各大堆大小分别为N1,N2,……Nk的一般的Nim博弈。将每一个数Ni表示为其二进制数(数的位数相等,不等时在前面补0):
  N1 = as…a1a0
  N2 = bs…b1b0
  ……
  Nk = ms…m1m0
  如果每一种大小的子堆的个数都是偶数,我们就称Nim博弈是平衡的,而对应位相加是偶数的称为平衡位,否则称为非平衡位。因此,Nim博弈是平衡的,当且仅当:
  as +bs + … + ms 是偶数,即as XOR bs XOR … XOR ms  = 0
  ……
  a1 +b1 + … + m1 是偶数,即a1 XOR b1 XOR … XOR m1 = 0
  a0 +b0 + … + m0是偶数,即a0 XOR b0 XOR … XOR m0 = 0  
于是,我们就能得出尼姆博弈中先手获胜策略:
Bouton定理先手能够在非平衡尼姆博弈中取胜,而后手能够在平衡的尼姆博弈中取胜。
上模板题:

Matches Game

 POJ - 2234 
Here is a simple game. In this game, there are several piles of matches and two players. The two player play in turn. In each turn, one can choose a pile and take away arbitrary number of matches from the pile (Of course the number of matches, which is taken away, cannot be zero and cannot be larger than the number of matches in the chosen pile). If after a player’s turn, there is no match left, the player is the winner. Suppose that the two players are all very clear. Your job is to tell whether the player who plays first can win the game or not.
Input
The input consists of several lines, and in each line there is a test case. At the beginning of a line, there is an integer M (1 <= M <=20), which is the number of piles. Then comes M positive integers, which are not larger than 10000000. These M integers represent the number of matches in each pile.
Output
For each test case, output "Yes" in a single line, if the player who play first will win, otherwise output "No".
Sample Input
2 45 45
3 3 6 9
Sample Output
No
Yes
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 using namespace std;
 5 int main()
 6 {
 7     int n;
 8     while(~scanf("%d",&n))
 9     {
10         int sum=0;
11 
12         for(int i=0;i<n;i++)
13         {
14             int x;
15             scanf("%d",&x);
16             sum^=x;
17         }//printf("%d\n",sum);
18         if(sum!=0)
19         {
20             printf("Yes\n");
21         }
22         else
23         {
24             printf("No\n");
25         }
26     }
27 }
深入一下,讲一下SG函数和SG定理;推荐极好的博客  戳我戳我
首先介绍一下PN状态:
必胜点和必败点的概念:
        P点:必败点,换而言之,就是谁处于此位置,则在双方操作正确的情况下必败。
 
     
   N点:必胜点,处于此情况下,双方操作均正确的情况下必胜。
 
     
必胜点和必败点的性质:
 
     
      1、所有终结点是必败点 P。(我们以此为基本前提进行推理,换句话说,我们以此为假设)
 
     
        2、从任何必胜点N 操作,至少有一种方式可以进入必败点 P。
 
     
        3、无论如何操作,必败点P 都只能进入必胜点 N。
 
     
大学英语四级考试就要来临了,你是不是在紧张的复习?也许紧张得连短学期的ACM都没工夫练习了,反正我知道的Kiki和Cici都是如此。当然,作为在考场浸润了十几载的当代大学生,Kiki和Cici更懂得考前的放松,所谓“张弛有道”就是这个意思。这不,Kiki和Cici在每天晚上休息之前都要玩一会儿扑克牌以放松神经。 
“升级”?“双扣”?“红五”?还是“斗地主”? 
当然都不是!那多俗啊~ 
作为计算机学院的学生,Kiki和Cici打牌的时候可没忘记专业,她们打牌的规则是这样的: 
1、  总共n张牌; 
2、  双方轮流抓牌; 
3、  每人每次抓牌的个数只能是2的幂次(即:1,2,4,8,16…) 
4、  抓完牌,胜负结果也出来了:最后抓完牌的人为胜者; 
假设Kiki和Cici都是足够聪明(其实不用假设,哪有不聪明的学生~),并且每次都是Kiki先抓牌,请问谁能赢呢? 
当然,打牌无论谁赢都问题不大,重要的是马上到来的CET-4能有好的状态。 

Good luck in CET-4 everybody! 

Input输入数据包含多个测试用例,每个测试用例占一行,包含一个整数n(1<=n<=1000)。Output如果Kiki能赢的话,请输出“Kiki”,否则请输出“Cici”,每个实例的输出占一行。 
Sample Input
1
3
Sample Output
Kiki
Cici
   n 1 2 3 4 5 6 ...
状态 P P N P P N.... 对于6来说,先手可以拿走1、2、4三种,给后手剩下5、4、2,然而后手面对5、4、2都必胜的,所以先手必败。
所以这是有规律的,找出规律,就变得简单了。
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 using namespace std;
 5 int main()
 6 {
 7    int n;
 8    while(~scanf("%d",&n))
 9    {
10        if(n%3==0)
11         puts("Cici");
12        else
13         puts("Kiki");
14    }
15    return 0;
16 }
再来一个稍微复杂一点的: hdu 2147 kiki's game
Recently kiki has nothing to do. While she is bored, an idea appears in his mind, she just playes the checkerboard game.The size of the chesserboard is n*m.First of all, a coin is placed in the top right corner(1,m). Each time one people can move the coin into the left, the underneath or the left-underneath blank space.The person who can't make a move will lose the game. kiki plays it with ZZ.The game always starts with kiki. If both play perfectly, who will win the game? 
InputInput contains multiple test cases. Each line contains two integer n, m (0<n,m<=2000). The input is terminated when n=0 and m=0. 

OutputIf kiki wins the game printf "Wonderful!", else "What a pity!". 
Sample Input
5 3
5 4
6 6
0 0
Sample Output
What a pity!
Wonderful!
Wonderful!
画出NP关系图如图:
P N P N P N P
N N N N N N N
P N P N P N P
N N N N N N N
P N P N P N P
N N N N N N N
P N P N P N P

 1 #include <cstdio>
 2 #include <cstdlib>
 3 #include <cstring>
 4 #include <algorithm> 
 5 using namespace std; 
 6 int main(){
 7     int n,m;
 8     while(scanf("%d %d",&n,&m)!=EOF){
 9         if(n==0 && m==0)break;
10         if(n%2==0 || m%2==0){
11             printf("Wonderful!\n");
12         }else{
13             printf("What a pity!\n");
14         }
15     }
16     return 0;
17 }
 
     

  现在我们就来介绍今天的主角吧。组合游戏的和通常是很复杂的,但是有一种新工具,可以使组合问题变得简单————SG函数和SG定理。

Sprague-Grundy定理(SG定理):

        游戏和的SG函数等于各个游戏SG函数的Nim和。这样就可以将每一个子游戏分而治之,从而简化了问题。而Bouton定理就是Sprague-Grundy定理在Nim游戏中的直接应用,因为单堆的Nim游戏 SG函数满足 SG(x) = x。

SG函数:

        首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。

        对于任意状态 x ,定义 SG(x) = mex(S),其中 S 是 x 后继状态的SG函数值的集合。如 x 有三个后继状态分别为 SG(a),SG(b),SG(c),那么SG(x) = mex{SG(a),SG(b),SG(c)}。 这样集合S 的终态必然是空集,所以SG函数的终态为 SG(x) = 0,当且仅当 x 为必败点P时。

【实例】取石子问题

有1堆n个的石子,每次只能取{ 1, 3, 4 }个石子,先取完石子者胜利,那么各个数的SG值为多少?

SG[0]=0,f[]={1,3,4},

x=1 时,可以取走1 - f{1}个石子,剩余{0}个,所以 SG[1] = mex{ SG[0] }= mex{0} = 1;

x=2 时,可以取走2 - f{1}个石子,剩余{1}个,所以 SG[2] = mex{ SG[1] }= mex{1} = 0;

x=3 时,可以取走3 - f{1,3}个石子,剩余{2,0}个,所以 SG[3] = mex{SG[2],SG[0]} = mex{0,0} =1;

x=4 时,可以取走4-  f{1,3,4}个石子,剩余{3,1,0}个,所以 SG[4] = mex{SG[3],SG[1],SG[0]} = mex{1,1,0} = 2;

x=5 时,可以取走5 - f{1,3,4}个石子,剩余{4,2,1}个,所以SG[5] = mex{SG[4],SG[2],SG[1]} =mex{2,0,1} = 3;

以此类推.....

   x        0  1  2  3  4  5  6  7  8....

SG[x]    0  1  0  1  2  3  2  0  1....

由上述实例我们就可以得到SG函数值求解步骤,那么计算1~n的SG函数值步骤如下:

1、使用 数组f 将 可改变当前状态 的方式记录下来。

2、然后我们使用 另一个数组 将当前状态x 的后继状态标记。

3、最后模拟mex运算,也就是我们在标记值中 搜索 未被标记值 的最小值,将其赋值给SG(x)。

4、我们不断的重复 2 - 3 的步骤,就完成了 计算1~n 的函数值。

代码实现如下:

 
    

猜你喜欢

转载自www.cnblogs.com/1013star/p/9748577.html