常见的博弈论专题详解(附有例题)

一、巴什博弈(Bash game)

1、概念

 巴什博弈是一种较为简单的减法博弈(Subtraction game),减法博弈的共同特征为玩家轮流从某一总数(对应n件物品)中减去某个数值(对应拿取物品),所减去的数值限定在某个集合中(对应1到m),先将数值减为0者(先拿完物品者)获胜。

2、常见的形式

a.有一堆总数为n的物品,2名玩家轮流从中拿取物品。每次至少拿1件,至多拿m件,不能不拿,最终将物品拿完者获胜。

b.两人轮流报数,先报数的必须报1到m之间的正整数(包含1或m),后面所报数则必须比前一个人所报数大1到m(包含1或m),先说出n者获胜。

3.对应策略

在先取完者胜的巴什博弈中,若n可被m+1整除,则后手方必胜,否则先手方必胜。

原理如下:

先考虑最简单的局面

n

设n=k(m+1)+r(所有的n),其中0<=r<=m;

a.如果r=0,n=k(m+1),若先手取x个,则后手可以对应取(m+1-x)个,则一直处于后手必胜的局面;

b.如果r不等于0,n=k(m+1)+r,则处于先手必胜的局面;

4.核心代码+例题

题目要求:

各位勇敢者要玩的第一个游戏是什么呢?很简单,它是这样定义的:
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     first

4 3       second

#include
using namespace std;
int n,t,m;
int main()
{
	cin>>t;
	while(t--)
	{
		cin>>n>>m;
		if(n%(m+1)==0)
		{
			printf("second\n");//后手必胜 
		}
		else
		{
			
			printf("first\n");//先手必胜 
		}
	}
	return 0;
}

二、尼姆博弈

1.概念

尼姆博弈中涉及到n堆不同的物品,这些堆中各自物品的数量是任意的。两名玩家在轮流行动时,可以选择将某一堆中任意数量的物品拿走,至少1个,至多全部拿走,但不能不拿或跨堆拿取物品。根据规则拿到最后一个物品,使得对手无物品可拿的玩家获胜。

2.常见形式

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

3.对应策略

若物品堆的尼姆和为0,则后手有必胜策略,否则先手有必胜策略

实现步骤

1.计算尼姆和sum;

2.尼姆和sum=0,则后手必胜

3.尼姆和sum不等于0,则计算sum与每一个物品堆xi的数量的异或值,即:sum⊕xi,直到找到一个(sum⊕xi)

xi-(sum⊕xi)个物品数量,方可以处于先手必胜的局面。

4.延伸(若拿到最后一个物品者必败)

    如果将规则改为拿到最后一个物品者败,可得到尼姆博弈的一种变体。有下面的结论:

  1. 若尼姆和为0且所有堆中仅有1个物品,则先手必胜策略;

  2. 若尼姆和不为0且至少有一堆物品数量大于1,则先手有必胜策略;

  3. 否则后手方有必胜策略。

5.核心代码+例题

题目1:

一年在外 父母时刻牵挂
春节回家 你能做几天好孩子吗
寒假里尝试做做下面的事情吧
陪妈妈逛一次菜场
悄悄给爸爸买个小礼物
主动地 强烈地 要求洗一次碗
某一天早起 给爸妈用心地做回早餐
如果愿意 你还可以和爸妈说
咱们玩个小游戏吧 ACM课上学的呢~
下面是一个二人小游戏:桌子上有M堆扑克牌;每堆牌的数量分别为Ni(i=1…M);两人轮流进行;每走一步可以任意选择一堆并取走其中的任意张牌;桌子上的扑克全部取光,则游戏结束;最后一次取牌的人为胜者。
现在我们不想研究到底先手为胜还是为负,我只想问大家:
――“先手的人如果想赢,第一步有几种选择呢?”

输入:

输入数据包含多个测试用例,每个测试用例占2行,首先一行包含一个整数M(1

输出:

如果先手的人能赢,请输出他第一步可行的方案数,否则请输出0,每个实例的输出占一行。

样例:

Sample

Inputcopy Outputcopy
3
5 7 9
0 
1 
#include
#define int long long
using namespace std;
const int N=110;
int n;
int a[N];
void init()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
}
signed main()
{
	init();
	while(cin>>n&&n!=0)
	{
		int i,num;
		int f=0;
		int sum=0;
		for(i=1;i<=n;i++)
		{
			cin>>a[i];
			sum^=a[i];
		}
		if(sum==0)
		{
			cout<<"0"<<'\n'; //后手必胜 
		}
		else
		{
			int cnt=0;//先手能赢,第一步可行的方案数,cnt记录方案数量
			for(i=1;i<=n;i++)
			{
				if(a[i]>(a[i]^sum))//(ps:注意>的优先级大于^) 
				{
					cnt++;
				}
			}
			cout<

题目2:

m堆石子,两人轮流取.只能在1堆中取.取完者胜.先取者负输出No.先取者胜输出Yes,然后输出怎样取子.例如5堆 5,7,8,9,10先取者胜,先取者第1次取时可以从有8个的那一堆取走7个剩下1个,也可以从有9个的中那一堆取走9个剩下0个,也可以从有10个的中那一堆取走7个剩下3个.

输入:

输入有多组.每组第1行是m,m<=200000. 后面m个非零正整数.m=0退出.

输出:

先取者负输出No.先取者胜输出Yes,然后输出先取者第1次取子的所有方法.如果从有a个石子的堆中取若干个后剩下b个后会胜就输出a b.参看Sample Output.

Sample

Inputcopy Outputcopy
 2 
 45 45
 3
 3 6 9
 5
 5 7 8 9 10
 0
 No
 Yes
 9 5
 Yes 
 8 1 
 9 0 
 10 3

思路:

1.先手输,输出No;

2.先手胜利,输出Yes,然后输出胜利的方案

#include
using namespace std;
const int N=200010;
int a[N];
int n;
void init()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
}
int main()
{
	init();
	while(cin>>n&&n!=0)
	{
		int i;
		int sum=0;
		for(i=1;i<=n;i++)
		{
			cin>>a[i];
			sum^=a[i];
		}
		if(sum==0)
		{
			printf("No\n");
		}
		else
		{
			printf("Yes\n");
			int ans=0;
			for(i=1;i<=n;i++)
			{
				if(a[i]>(sum^a[i]))
				{
					int p=(sum^a[i]);//取走后剩下的数量
					printf("%d %d\n",a[i],p);
				}
			}
		}
	}
	return 0;
} 

三、威佐夫博弈

1.概念

威佐夫博弈(Wythoff's game):有两堆各若干个物品,两个人轮流从任一堆取至少一个或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

2.常见形式

游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。

3.对应策略

两个人如果都采用正确操作,那么面对非奇异局势,先手必胜;反之,后手必胜

a.黄金分割数:1.618=(√5+1)/2 

1.618=(sqrt(5.0)+1)/2

b.公式:

double ans=(int)((b-a)*((sqrt(5.0)+1)/2));

4.核心代码+例题

题目:

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

输入:

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

输出:

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

Sample

Inputcopy Outputcopy
2 1
8 4 
4 7
0 
1 
0

#include
#define int long long
using namespace std;
signed main()
{
	double a,b;
	while(cin>>a>>b)
	{
		if(a>b)
		{
			swap(a,b);
		}
		double ans=(int)((b-a)*((sqrt(5.0)+1)/2));
		if(a==ans)
		{
			cout<<"0"<<'\n'; 
	    }
	    else
	    {
	    	cout<<"1"<<'\n';
		}
	}
	return 0;
}

你可能感兴趣的:(c++算法学习,ACM实验室---周赛训练题,开发语言,算法,c++,青少年编程)