阶梯博弈(另类尼姆博弈)

今天看到挑战程序设计上有一题博弈叫阶梯博弈,可以另类表示成尼姆博弈,感觉很有趣,于是去看了想相关的博文。

参考了这篇博客:我谈阶梯博弈( Staircase Nim )

阶梯博弈:博弈在一列阶梯上进行,每个阶梯上放着自然数个点,两个人进行阶梯博弈,每一步则是将一个集体上的若干个点

( >=1 )移到前面去,最后没有点可以移动的人输。

说的通俗点,是在一排阶梯上的博弈。 每列阶梯上都有若干个石子,如下图所示,两名玩家可以向左移动石子到相邻阶梯上,最后谁把所有石子移动到地面上谁就获胜。

把阶梯都如图中所示标上序号(地面为0),那么我们可以把所有奇数堆石子看成N堆石子,然后当成尼姆博弈来求解。下面让我们来解释一下为什么可以这么写。

我们先按尼姆博弈的角度看这道题,设sum=a1^a2^.....an,如果当前的sum!=0,也就是先手必胜。那么先手就只对奇数堆进行操作,如果后一个人也对奇数堆进行操作,相当于在进行尼姆博弈。那么偶数堆的影响呢?假设后一个人对偶数堆进行操作将石子移动到奇数堆,企图改变先手的必胜态,但是下一次轮到先手时,只要将奇数堆里多出来的石子移动到偶数堆里去,就又恢复到了原来的状态,所以对偶数堆的操作并不会改变奇数堆的状态,也就不会影响到尼姆博弈的到过程,可以将偶数堆忽略。如果先手必败呢?同理,如果先手移动偶数堆,后手会从奇数堆移出相同数量的石子,没有影响,只要判断奇数堆做尼姆博弈的情况即可。

为什么是只对奇数堆做Nim就可以而不是偶数堆呢?因为如果是对偶数堆做Nim,对手移动奇数堆的石子到偶数堆,我们跟着移动这些石子到下一个奇数堆,那么最后是对手把这些石子移动到了0,我们不能继续跟着移动。那这样一来奇数堆就对偶数堆的Nim就有了影响,就不能只对偶数堆做Nim来比赛了。

 

例题一

POJ 1704 Georgia and Bob

题目链接:http://poj.org/problem?id=1704

题目大意:有一行排成直线的格子上放有n个棋子,如下图所示:

Georgia和Bob轮流选择一个棋子向左移动,每次可以移动一个及以上任意多格,但是不允许反超其他的棋子,也不允许将两个棋子放在同一个格子内。无法进行移动操作的一方失败,问谁会赢?

解题思路:阶梯博弈,当棋子有偶数个时,我们可以将棋子两两当成Nim中的一堆石子,两棋子中间的格子数为堆的石子数。如下图所示:

 

那就跟上面介绍的阶梯博弈一样了,把棋子组成的堆的石子数看成奇数列阶梯石子数,堆与堆之间的间隔看成偶数列阶梯的石子数,最终目的是把所有的石子都移到最右边(地面)。

当棋子有奇数个时,我们要进行特殊处理,将多出来的一个棋子与棋盘最左侧组成一堆,如下图所示:

所以最后解法就像Nim一样对每堆的石子数进行异或求和,最后判断异或值如果不为0,则先手胜,反之后首胜。注意一下,题目给出的棋子位置不是从小到大给的,所以要自己排下序,坑。

代码:

 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 const int N=1e3+5;
 5 int a[N];
 6 
 7 int main(){
 8     int T;
 9     scanf("%d",&T);
10     while(T--){
11         int n; 
12         scanf("%d",&n);
13         for(int i=1;i<=n;i++){
14             scanf("%d",&a[i]);
15         }
16         //n为奇数就将棋盘最左侧当成一个棋子 
17         if(n%2==1)
18             a[++n]=0; 
19         sort(a+1,a+1+n);
20         int sum=0;
21         for(int i=2;i<=n;i+=2){
22             sum^=(a[i]-a[i-1]-1);
23         }
24         if(sum!=0)
25             puts("Georgia will win");
26         else
27             puts("Bob will win");
28     }
29 } 

 例题二

HDU 4315 Climbing the Hill

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4315

题目大意:山上有n个人,Alice和Bob轮流操作,每次可以选择一个人向上移动任意距离,但不能超过前面的人。山顶(坐标为0)可以站多个人,其他格子只能站一个人,在距离山顶第k远的位置的人时king,谁能先把king送上山顶谁就是获胜者。如下图为样例:

3 3

1 2 4

 

解题思路:这个题目跟上面的移动棋子的问题很像,如果没有king,那么解法就是一样的偶数时棋子两两成堆,奇数时令山顶与第一个人形成第一个区间(a[0]=-1),用Nim求解。但是多了king的话就需要多一些特殊判断,k==1时,Alice直接获胜,n为奇数且k==2时,因为谁先移动第一颗棋子到山顶谁就会输,除了没得选择谁都不会这么做,所以要把第一个区间距离-1(a[0]=-1+1=0)。

代码:

 1 #include<cstdio>
 2 const int N=1e3+5;
 3 
 4 int a[N];
 5 
 6 int main(){
 7     int n,k;
 8     while(~scanf("%d%d",&n,&k)){
 9         for(int i=1;i<=n;i++){
10             scanf("%d",&a[i]);
11         }
12         
13         if(k==1){
14             puts("Alice");
15             continue;
16         }
17         int sum=0;
18         //n为奇数,让第一个棋子与最顶上形成第一个区间 
19         if(n%2==1){
20             //k==2时,第一个区间距离-1 
21             a[0]=-1+(k==2);
22             for(int i=1;i<=n;i+=2)
23                 sum^=(a[i]-a[i-1]-1);
24         }
25         else{
26             for(int i=2;i<=n;i+=2)
27                 sum^=(a[i]-a[i-1]-1);
28         }
29         if(sum!=0)
30             puts("Alice");
31         else
32             puts("Bob");
33     }
34 } 

 

posted @ 2017-09-03 21:31  Yeader  阅读(1097)  评论(0编辑  收藏  举报