博弈问题总结(基础篇)

博弈问题总结(基础篇)

前言

最近做的博弈问题的题比较多,所以我就汇总了一下博弈问题的几种题型,方便之后的做题

博弈论定义

博弈论就是指有若干个人进行一些对弈,并且默认每个人都是最聪明的,不会失误,都可以找到当前的最优解,然后来寻找有没有哪个人有必胜/必败的的策略。

A、尼姆博弈

为什么叫尼姆博弈呢?因为这是尼姆(英文名:Nimm Game)发明的数学游戏。

博弈模型

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

分析

我们先考虑简单的情况

1、n=1

这时先手必胜,因为他只需要把唯一的这一堆石子取走就可以了

2、n=2

若a[1]=a[2],先手必败,因为无论先手在哪一堆石子中取走几个,后手总能在另一堆石子中取走相同的个数

若a[1]!=a[2],我们假设a[1]>a[2],此时先手必胜,因为先手可以在第一堆石子中取走a[1]-a[2]个,这时两堆石子的个数相同,下一次无论后手取走多少个,先手都可以在另一堆取走同样多个,因此先手必胜

若a[1]

3、要是n=3或者更大呢?

我们显然不能像上面一样去枚举每种情况,所以我们要得出一个更为一般的结论

我们设总共有n堆石子,每一堆石子的个数分别为a[1]、a[2]、a[3]……a[n]

若a[1] ^ a[2] ^ a[3] ^ …… ^ a[n] =0先手必败,反之先手必胜

下面是证明

如果异或和的最高位为i,那么必定有一堆石子的第 i 位为1

我们设这一堆石子的个数为k,其它所有石子的异或和为m,总异或和为x

则必定有k ^ m=x,我们把这一堆石子变成k^x

(k ^ x) ^ m=0

这时,所有石子的异或和都变成了0

举个例子:11001 ^ 11100=00101,则有(11001 ^ 00101)^ 11100=0

如果当前所有数字的异或和为0,那么下一次无论你怎么取石子,异或和一定不会为0

这样我们可以得出结论:如果先手异或和不为0,可以一步让后手的情况为异或和为0;如果先手异或和为0,那么后手异或和就不为0

这样,我们不断进行游戏,最终一定会达到所有的数都为0的情况,而最后面对这种情况的一定会输

所以我们可以得出结论:若a[1] ^ a[2] ^ a[3] ^ …… ^ a[n] =0先手必败,反之先手必胜

例题

洛谷P2197模板题(好裸的板子

题意

甲,乙两个人玩 Nim 取石子游戏。

Nim 游戏的规则是这样的:地上有 n 堆石子(每堆石子数量小于 104),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取。每次只能从一堆里取。最后没石子可取的人就输了。假如甲是先手,且告诉你这 n 堆石子的数量,他想知道是否存在先手必胜的策略。

输入格式

第一行一个整数 T(T≤10),表示有 T* 组数据

接下来每两行是一组数据,第一行一个整数 n,表示有 n 堆石子,n≤10000

第二行有 n个数,表示每一堆石子的数量.

输出格式

共 T行,如果对于这组数据存在先手必胜策略则输出 Yes,否则输出 No,每个单词一行。

样例

输入

2
2
1 1
2
1 0

输出

No
Yes
分析

没什么好分析的,就是一个板子,直接上代码

代码(markdown写代码真不错
#include
using namespace std;
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int n;
        scanf("%d",&n);
        int ans=0;
        for(int i=1;i<=n;i++){
            int aa;
            scanf("%d",&aa);
            ans^=aa;
        }
        if(ans==0) printf("No\n");
        else printf("Yes\n");
    }
    return 0;
}

B、巴什博弈

博弈模型

一堆物品有n个,两个顶尖聪明的人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。

分析

和尼姆博弈相比,巴什博弈只有一堆石子,而且加上了取的石子数的限制

如果石子数只有m个的话,那么先手必胜,因为它可以一次性把所有的石子取完

如果石子数有m+1个的话,那么先手必负,因为第一次先手无论取走多少石子,后手都可以一次性将其取完

如果石子数有m+1+n个(n

我们不难发现,面临m+1个石子的人一定失败。

这样的话两个人的最优策略一定是通过拿走石子,使得对方拿石子时还有m+1个

我们再向一般情况推广

当n=(m+1)\(\times\)k时,先手必负,因为对于每m+1个石子,先手取走k个后,后手总能取走v个,使得k+v=m+1,这样,先手最终会面临m+1的必负情况

当n\(\neq\)(m+1)\(\times\)k时,先手在第一次可以拿走一定的石子,使得n可以被(m+1)整除,这样就转变成了上面的情况

所以结论为:当n能整除m+1时先手必败,否则先手必胜。

例题

1、HDU 1864 Brave Game

题意

十年前读大学的时候,中国每年都要从国外引进一些电影大片,其中有一部电影就叫《勇敢者的游戏》(英文名称:Zathura),一直到现在,我依然对于电影中的部分电脑特技印象深刻。
今天,大家选择上机考试,就是一种勇敢(brave)的选择;这个短学期,我们讲的是博弈(game)专题;所以,大家现在玩的也是“勇敢者的游戏”,这也是我命名这个题目的原因。
当然,除了“勇敢”,我还希望看到“诚信”,无论考试成绩如何,希望看到的都是一个真实的结果,我也相信大家一定能做到的~

各位勇敢者要玩的第一个游戏是什么呢?很简单,它是这样定义的:
1、 本游戏是一个二人游戏;
2、 有一堆石子一共有n个;
3、 两人轮流进行;
4、 每走一步可以取走1…m个石子;
5、 最先取光石子的一方为胜;

如果游戏的双方使用的都是最优策略,请输出哪个人能赢。

输入格式

输入数据首先包含一个正整数C(C<=100),表示有C组测试数据。
每组测试数据占一行,包含两个整数n和m(1<=n,m<=1000),n和m的含义见题目描述。

输出格式

如果先走的人能赢,请输出“first”,否则请输出“second”,每个实例的输出占一行。

样例

样例输入

2
23 2
4 3

样例输出

first
second
分析

裸的板子,没什么好说的

代码
#include
using namespace std;
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);
        int ans=n%(m+1);
        if(ans==0) printf("second\n");
        else printf("first\n");
    }
    return 0;
}

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

题意

对于四川同胞遭受的灾难,全国人民纷纷伸出援助之手,几乎每个省市都派出了大量的救援人员,这其中包括抢险救灾的武警部队,治疗和防疫的医护人员,以及进行心理疏导的心理学专家。根据要求,我校也有一个奔赴灾区救灾的名额,由于广大师生报名踊跃,学校不得不进行选拔来决定最后的人选。经过多轮的考核,形势逐渐明朗,最后的名额将在“林队”和“徐队”之间产生。但是很巧合,2个人的简历几乎一模一样,这让主持选拔的8600很是为难。无奈,他决定通过捐款来决定两人谁能入选。
选拔规则如下:
1、最初的捐款箱是空的;
2、两人轮流捐款,每次捐款额必须为正整数,并且每人每次捐款最多不超过m元(1<=m<=10)。
3、最先使得总捐款额达到或者超过n元(0 我们知道,两人都很想入选志愿者名单,并且都是非常聪明的人,假设林队先捐,请你判断谁能入选最后的名单?

输入格式

输入数据首先包含一个正整数C,表示包含C组测试用例,然后是C行数据,每行包含两个正整数n,m,n和m的含义参见上面提到的规则。

输出格式

对于每组测试数据,如果林队能入选,请输出字符串"Grass", 如果徐队能入选,请输出字符串"Rabbit",每个实例的输出占一行。

样例

样例输入

2
8 10
11 10

样例输出

Grass
Rabbit
分析

上一道题的代码改改输出就能过了

这两道板子题主要是为了找自信

代码
#include
using namespace std;
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);
        int ans=n%(m+1);
        if(ans==0) printf("Rabbit\n");
        else printf("Grass\n");
    }
    return 0;
}

3、Ticket Game CodeForces - 1215D 博弈题

题目描述

Monocarp and Bicarp live in Berland, where every bus ticket consists of n digits (n is an even number). During the evening walk Monocarp and Bicarp found a ticket where some of the digits have been erased. The number of digits that have been erased is even.

Monocarp and Bicarp have decided to play a game with this ticket. Monocarp hates happy tickets, while Bicarp collects them. A ticket is considered happy if the sum of the first \(\frac{n}{2}\) digits of this ticket is equal to the sum of the last \(\frac{n}{2}\) digits.

Monocarp and Bicarp take turns (and Monocarp performs the first of them). During each turn, the current player must replace any erased digit with any digit from 0 to 9. The game ends when there are no erased digits in the ticket.

If the ticket is happy after all erased digits are replaced with decimal digits, then Bicarp wins. Otherwise, Monocarp wins. You have to determine who will win if both players play optimally.

输入格式

The first line contains one even integer n (2≤n≤2⋅105) — the number of digits in the ticket.

The second line contains a string of n digits and "?" characters — the ticket which Monocarp and Bicarp have found. If the ii-th character is "?", then the ii-th digit is erased. Note that there may be leading zeroes. The number of "?" characters is even.

输出格式

If Monocarp wins, print "Monocarp" (without quotes). Otherwise print "Bicarp" (without quotes).

样例

Input

4
0523

Output

Bicarp

Input

2
??

Output

Bicarp

Input

8
?054??0?

Output

Bicarp

Input

6
???00?

Output

Monocarp
分析

一句话题意:一张票有n位数,如果这张票的前一半数字的和等于后一半数字的和(n一定是偶数),就称这张票为快乐票。有些数被擦除了,标记为’?’(’?‘的个数也是偶数),现在Monocarp 和 Bicarp 进行一个游戏,两人轮流将’?'变换成0到9的任意一个数,Monocarp先手,如果最后票为快乐票则Bicarp赢,否则Monocarp赢。

其实就是巴什博弈变了一下型,思想是一样的

我们分情况来考虑一下

1、左边的数字之和等于右边的数字之和

这时,如果左右两边?的个数相等的话,后手赢,因为先手无论放什么数,后手都可以放一个相同的数来平衡

如果不相等,则先手必胜,因为最后肯定只能在一边放,只要先手能放,就一定会打破平衡

2、左边的数字之和小于右边的数字之和

如果左边?的个数大于等于右边的个数,那么先手必胜,因为先手可以一直在左边放9,后手只能不断在右边放9维持差距,但始终不能弥补差距

如果左边?的个数小于右边的个数,我们设左边?的个数为a,右边?的个数为b,那么前2a回合,策略同上;2a回合之后,先手放一个数x,后手唯一的选择就是放一个y,使x+y=9,所以当左右两边数字之差为9的倍数时,后手胜,否则先手胜

3、右边的数字之和小于左边的数字之和(同上)

代码
#include
#include
#include
#include
#include
#include
using namespace std;
char c[300000];
int lefw,rigw,lef,rigt;
int main(){
    int n;
    scanf("%d",&n);
    getchar();
    for(int i=1;i<=n/2;i++){
        scanf("%c",&c[i]);
        if(c[i]=='?'){
            lefw++;
        } else {
            lef+=(c[i]-'0');
        }
   }
   for(int i=n/2+1;i<=n;i++){
        scanf("%c",&c[i]);
        if(c[i]=='?'){
            rigw++;
        } else {
            rigt+=(c[i]-'0');
        }
   }
   if(lef==rigt){
        if(lefw==rigw) printf("Bicarp\n");
        else printf("Monocarp\n");
    }
    else if(lef>rigt){
       int cha=rigw-lefw;
       if(cha<=0){
            printf("Monocarp\n");
            return 0;
       }
       int chaa=lef-rigt;
       if(cha%2==0 && cha/2*9==chaa) printf("Bicarp\n");
        else printf("Monocarp\n");
    } else {
        int cha=lefw-rigw;
       if(cha<=0){
            printf("Monocarp\n");
            return 0;
       }
       int chaa=rigt-lef;
       if(cha%2==0 && cha/2*9==chaa) printf("Bicarp\n");
        else printf("Monocarp\n");
    }
    return 0;
}

C、威佐夫博弈

博弈模型

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

分析

我们先来考虑一下什么情况下先手必负

一、石子状态为(0,0)

很显然,先手必负

二、石子状态为(1,2)

如果先手把左面那一堆石子全部拿走,那么后手可以把右面那一堆石子全部拿走,先手负

如果先手把右面那一堆石子全部拿走,那么后手可以把左面那一堆石子全部拿走,先手负

如果先手只在右面拿一个石子,那么后手可以一次性把两堆石子都取走,先手负

如果先手在左面和右面各取一个石子,那么后手可以把右面的石子全部拿走,先手负

三、石子状态为(3,5)

如果先手在左面的石子中取1个,那么后手可以在右面那一堆中取4个,这样状态就变成了(1,2),先手负

如果先手在左面的石子中取2个,那么后手可以在右面的石子中取3个,状态又变成了(1,2),先手负

如果先手把左面的石子全部取完,那么后手也可以把右面的石子全部取完,先手负

如果先手在右面的石子中取1个,那么后手可以同时在两堆石子中取2个,状态又变成了(1,2),先手负

如果先手在右面的石子中取2个,那么后手可以同时在两堆石子中取3个,先手负

如果先手在右面的石子中取3个,那么后手可以同时在两堆石子中取1个,状态又变成了(1,2),先手负

如果先手在右面的石子中取4个,那么后手可以在左面的石子中取1个,状态又变成了(1,2),先手负

……

总而言之,这种情况先手负

那我们可以发现一个规律,下面的情况先手必负

第一种(0,0)

第二种(1,2)

第三种(3,5)

第四种 (4 ,7)

第五种(6,10)

第六种 (8,13)

第七种 (9 , 15)

第八种 (11 ,18)

……

第n种(a,b)

我们可以发现左右两堆的差值是逐渐递增的

而且左边的数是之前没有出现过的最小的自然数

而且更重要的是差值$\times$1.618=左边的数

而且我们知道黄金分割数=\(\frac{\sqrt{5}-1}{2}\)=0.618

所以这个比例就是黄金分割数+1(是不是很神奇)

例题

HDU 1527 取石子游戏

题目描述

有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。

输入格式

输入包含若干行,表示若干种石子的初始情况,其中每一行包含两个非负整数a和b,表示两堆石子的数目,a和b都不大于1,000,000,000。

输出格式

输出对应也有若干行,每行包含一个数字1或0,如果最后你是胜者,则为1,反之,则为0。

样例

Input

2 1
8 4
4 7

Output

0
1
0
分析

模板题

代码
#include
#include
#include
#include
using namespace std;
int main(){
    int a,b;
    double r=(sqrt(5.0)+1)/2;
    while(scanf("%d%d",&a,&b)!=EOF){
        if(a>b) swap(a,b);
        int c=b-a;
		int now=c*r;
        if(now==a){
            printf("0\n");
        } else {
            printf("1\n");
        }
    }
    return 0;
}

总结

其实博弈问题只要掌握了模板,有了思路,解题就比较容易了

你可能感兴趣的:(博弈问题总结(基础篇))