简单博弈小结

博弈小结

博弈的题目接触的不是很多,先在这里简单总结一下三种最基础的博弈吧。以后等做到相应的题目了再补充。


一:巴什博弈

有一堆石子,石子个数为n,两人轮流从石子中取石子出来,最少取一个,最多取m个。最后不能取的人输,问你最后的输赢情况。


这种就是可以直接判断必败态的问题。如果这堆石子少于或者等于m个,那么先手赢。如果石子数目为m+1个,那么先手必输,因为无论先手怎么拿石子,后手都可以直接把剩下的石子全部拿走。如果石子数目为m+1<n<2(m+1),那么先手就可以拿走n-(m+1)个石子,使得对手面对m+1的必败态,这样先手必赢。所以如此推算下去,我们就知道当n%(m+1)==0时,先手输,否则先手赢。


先撸几道水题找点感觉吧:

Public Sale http://acm.bnu.edu.cn/bnuoj/problem_show.php?pid=6293 

题目大意:拍卖一块土地,两个人轮流加价,每次叫价在1~N元范围内,当大于等于M元时,就算竞拍成功。每次都是Lee先叫价,如果最后能够竞拍成功,输出第一次叫价时报的价格。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<set>
#include<ctype.h>
#include<algorithm>
#include<string>
#define PI acos(-1.0)
#define maxn 1000
#define INF 1<<25
#define mem(a, b) memset(a, b, sizeof(a))
typedef long long ll;
using namespace std;
int main ()
{
    int n, m;
    while(~scanf("%d%d", &m, &n))
    {
        int t = m % (n + 1);
        if (t == 0) puts("none");
        else
        {
            if (n >= m) // 如果N一开始就大于M,所有大于等于M的叫价都可以
            {
                for (int i = m; i < n; i++)
                    printf("%d ", i);
                printf("%d\n", n);
            }
            else printf("%d\n", t); //否则就直接输出t
        }
    }
    return 0;
}


悼念512汶川大地震遇难同胞――选拔志愿者 http://acm.hdu.edu.cn/showproblem.php?pid=2188

就是最基本的取石子游戏。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<set>
#include<ctype.h>
#include<algorithm>
#include<string>
#define PI acos(-1.0)
#define maxn 1000
#define INF 1<<25
#define mem(a, b) memset(a, b, sizeof(a))
typedef long long ll;
using namespace std;
int main ()
{
    int c, n , m;
    cin>>c;
    while(c--)
    {
        scanf("%d%d", &n, &m);
        if (n % (m +1) == 0) puts("Rabbit");
        else puts("Grass");
    }
    return 0;
}


二:威佐夫博弈

有两堆石子,石子数目分别为n和m,现在两个人轮流从两堆石子中拿石子,每次拿时可以从一堆石子中拿走若干个,也可以从两中拿走相同数量的石子,拿走最后一刻石子的人赢。

可以发现,前面几组奇异局势为(a,b) :(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)

这其中第k组的a为前面没有出现过的最小非负整数,而b = a + k 

两个人如果都采用正确操作,那么面对非奇异局势,先拿者必胜;反之,则后拿者取胜。
那么任给一个局势(a,b),怎样判断它是不是奇异局势呢?我们有如下公式:
ak =[k(1+√5)/2],bk= ak + k (k=0,1,2,...n 方括号表示取整函数)

详见:威佐夫博奕(百度百科)

接下来就撸几道题目吧,形式还是挺多的。

取石子游戏 http://acm.bnu.edu.cn/bnuoj/problem_show.php?pid=1186

题目大意:就是威佐夫博奕。。。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<set>
#include<ctype.h>
#include<algorithm>
#include<string>
#define PI acos(-1.0)
#define maxn 1000
#define INF 1<<25
#define mem(a, b) memset(a, b, sizeof(a))
typedef long long ll;
using namespace std;
int main ()
{
    int a, b;
    while(~scanf("%d%d", &a, &b))
    {
        if (a > b) swap(a, b);  // 交换a,b 保证a比b要小
        int k = b - a;
        int n = (1 + sqrt(5.0)) / 2 * k; // 通过k来计算a。
        if (a == n) puts("0");
        else puts("1");
    }
    return 0;
}



取(2堆)石子游戏 http://acm.hdu.edu.cn/showproblem.php?pid=2177

同样是取石头问题,不过如果先手能赢,要输出第一次取石子后所剩的情况。(如果在任意的一堆中取走石子能胜同时在两堆中同时取走相同数量的石子也能胜,先输出取走相同数量的石子的情况)

这道题目要输出威佐夫博弈从非奇异局势到奇异局势的转变方案。比直接判断复杂了很多,其实数据量不大,可以打表。不过还是本文采用推理的方式来解这道题。

思考过程:

1. 先判断能否从两堆中取出相同的石头,达到目的。一直写的很挫,最后用了枚举才过的。。后来看别人写的这块内容,又一次觉得自己好弱

2. 判断从一堆中取出石头,这里其实可以分为三种。假设一开始的时候为(a,b)。第一种是从b当中取走少量的,变为(a,b - k)。第二种是从b中取走大量的,变为(b - t, a)。第三种是从a当中取走一些,变为(a - t,b)

这里我竟然傻逼的用黄金分割率去做。用b*0.618来。必然丢精度啊。。后来还是上网看了很多这方面的文章,不过写的都不是很详细。估计是我太弱。。

(PS 这道题是special judge。。。。我之前还想着怎么把重复输出的去掉。。。坑爹)·

// 威佐夫博弈 + 输出方案
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<set>
#include<ctype.h>
#include<algorithm>
#include<string>
#define PI acos(-1.0)
#define maxn 1000
#define INF 1<<25
#define mem(a, b) memset(a, b, sizeof(a))
typedef long long ll;
using namespace std;
int main ()
{
    int a, b;
    while(scanf("%d%d", &a, &b))
    {
        if (a == 0 && b == 0) break;
        int k = b - a;
        int n = (1 + sqrt(5.0)) / 2 * k;
        if (a == n) puts("0");
        else
        {
            puts("1");
            if (a - n == b - n - k) printf("%d %d\n", n, n + k);    //两者之间的差值不变
            if (a == 0) puts("0 0");    //如果a等于0了,差值只能为0,即为(0,0)。
            for (int i = 1; i <=b; i++) //枚举a和b之间的差值,可能从1一直到b - 1
            {
                n = (1 + sqrt(5.0)) / 2 * i;
                int tmp = n + i;
                if (n == a) printf("%d %d\n", n, tmp);
                if (tmp == a) printf("%d %d\n",n, tmp);
                if (tmp == b) printf("%d %d\n", n, b);
            }
        }
    }
    return 0;
}

三:尼姆博奕

定义:有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
当剩下一堆石头时,一定是必胜态。当剩下两堆相同的石头时(n,n),一定是必败态。只要你取x,对手在另一堆里面取x,会得到(n - x,n - x)。至于三堆的就比较麻烦,可以知道(1,2,3)是必败态。
不过最后有个神奇的结论,就是当每堆石头取异或之后,不为0代表着必胜态。为0代表着必败态。

证明:
1、最后的状态,全为零,显然成立;
2、对于某个局面(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。
3、对于某个局面(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'不是一个合法的移动。证毕。

参考资料

接下来撸几道基础题吧。

Matches Gamehttp://poj.org/problem?id=2234

题目大意,给你n堆石子,每堆个数不同,两个人轮流从任意一堆当中取任意个石子。当其中一个人取走最后一堆石头时,获胜。问先手能不能获胜。
// 尼姆博奕
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<set>
#include<ctype.h>
#include<algorithm>
#include<string>
#define PI acos(-1.0)
#define maxn 1000
#define INF 1<<25
#define mem(a, b) memset(a, b, sizeof(a))
typedef long long ll;
using namespace std;
int main ()
{
    int m, sum = 0, n;
    while(~scanf("%d", &m))
    {
        sum = 0;
        while(m--)
        {
            scanf("%d", &n);
            sum ^= n;
        }
        if (!sum) puts("No"); // (0, 0, 0) 为必败态,即异或之后为0为必败态
        else puts("Yes");
    }
    return 0;
}

取(m堆)石子游戏 http://acm.hdu.edu.cn/showproblem.php?pid=2176

题目大意:给你n堆石子,每堆个数不同,两个人轮流从任意一堆当中取任意个石子。当其中一个人取走最后一堆石头时,获胜。问先手能不能获胜。如果能够获胜,求出能够获胜第一次取石子的情况。(a,b) a为该堆石头数,b为取完之后的数。
/*
    http://acm.hdu.edu.cn/showproblem.php?pid=2176 
    尼姆博奕 并且求输出方案
*/

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<set>
#include<ctype.h>
#include<algorithm>
#include<string>
#define PI acos(-1.0)
#define maxn 200000
#define INF 1<<25
#define mem(a, b) memset(a, b, sizeof(a))
typedef long long ll;
using namespace std;
int sg[maxn+10];
int main ()
{
    int m;
    while(scanf("%d", &m) && m)
    {
        int sum = 0;
        for (int i = 0; i < m; i++)
        {
            scanf("%d", sg + i);
            sum ^= sg[i];
        }
        if (!sum) puts("No");
        else
        {
            puts("Yes");
            for (int i = 0; i < m; i++)
            {
                                     // sum = sg[0] ^ sg[1] ^ sg[2] ^ …… ^sg[m - 1]
                int s = sum ^ sg[i]; // s = sum ^ sg[i] 我们可以知道,异或符合交换律,而两个相同的数异或后为0,而一个数与0异或不改变
                                     //因此,s相当于除了sg[i]之外的所有堆的个数异或
                if (s < sg[i]) printf("%d %d\n", sg[i], s);//如果s<sg[i]。便可以取走sg[i] - s颗石头,变为s颗。两两异或之后为0
            }
        }
    }
    return 0;
}
 

Being a Good Boy in Spring Festival http://acm.hdu.edu.cn/showproblem.php?pid=1850

题目大意,这次不求输出情况了。求能赢的方案数。
不贴代码了。

最后再加一个变形的尼姆博弈。(题目:Georgia and Bob)单独写一篇博客了。 传送门在此

你可能感兴趣的:(博弈,尼姆博弈,威佐夫博弈,巴什博弈)