博弈问题总结

转自:https://blog.csdn.net/howardemily/article/details/77844769

此类问题一般有如下特点:

 

1、博弈模型为两人轮流决策的非合作博弈。即两人轮流进行决策,并且两人都使用最优策略来获取胜利。

 

 

2、博弈是有限的。即无论两人怎样决策,都会在有限步后决出胜负。

 

 

3、公平博弈。即两人进行决策所遵循的规则相同。

 

在此先强调一下,博弈重在寻找必胜态和必败态.一般找到必胜态必败态许多问题就迎刃而解了.

必败态: 毫无疑问就是当前状态无论怎么操作都是必输.(也就是当前步无论怎么走到达的全是必胜态,即把必胜态扔给对方)

必胜态: 下一步的操作中存在至少一个必败态.(即我可以走这一步操作,把必败态给了对方)

 

 

几种基本的博弈类型: (只给出结论,证明见:点击打开链接)

 

1.巴什博奕

问题模型:只有一堆n个物品,两个人轮流从这堆物品中取物品,规定每次至少取一个,最多取m个,最后取光者得胜。)

      结论:n%(m+1)==0先手必败.否则先手必胜

     变形:条件不变,改为最后取光的人输。 结论:(n-1)%(m+1)==0 先手必败,否则必胜

 

2. 威佐夫博奕

 

问题模型:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,

最后取光者得胜。

结论: k=(b-a) (a>b) s = (double )( k * ( sqrt(5.0) + 1 ) / 2 ); s==a 先手必败,否则必胜

  3.尼姆博弈

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

    结论: 当石子堆数为n堆时,则推广为当对每堆的数目进行亦或之后值为零是必败态。

 4.sg函数

对于ICG游戏,我们可以将游戏中每一个可能发生的局面表示为一个点。并且若存在局面i和局面j,且j是i的后继局面

(即局面i可以转化为局面j),我们用一条有向边,从i出发到j,连接表示局面i和局面j的点。则整个游戏可以表示成为

一个有向无环图:


根据ICG游戏的定义我们知道,任意一个无法继续进行下去的局面为终结局面,即P局面(先手必败)。在上图中我们

可以标记所有出度为0的点为P点。

接着根据ICG游戏的两条性质,我们可以逆推出所有点为P局面还是N局面:

因此,对于任意一个ICG游戏,我们可以采取逆推的方法,标记出所有局面是N局面还是P局面。
但仅仅只是标记N、P,所能得到的信息太少,于是我们定义了Sg(Sprague-Grundy)函数:

对于一个游戏可能发生的局面x,我们如下定义它的sg值:
(1)若当前局面x为终结局面,则sg值为0。
(2)若当前局面x非终结局面,其sg值为:sg(x) = mex{sg(y) | y是x的后继局面}。
mex{a[i]}表示a中未出现的最小非负整数。举个例子来说:
mex{0, 1, 2} = 3, mex{1, 2}=0, mex{0,1,3}=2

我们将上图用sg函数表示后,得到:

可以发现,若一个局面x为P局面,则有sg(x)=0;否则sg(x)>0。同样sg值也满足N、P之间的转换关系:
若一个局面x,其sg(x)>0,则一定存在一个后续局面y,sg(y)=0。
若一个局面x,其sg(x)=0,则x的所有后续局面y,sg(y)>0。

由上面的推论,我们可以知道用N、P-Position可以描述的游戏用sg同样可以描述。并且在sg函数中

还有一个非常好用的定理,叫做sg定理:
对于多个单一游戏,X=x[1..n],每一次我们只能改变其中一个单一游戏的局面。则其总局面的sg值

等于这些单一游戏的sg值异或和。
即:
sg(X) = sg(x[1]) xor sg(x[2]) xor … xor sg(x[n])
要证明这一点我们只要证明:
(1) 假设sg(x[1]) xor sg(x[2]) xor … xor sg(x[n]) = A,对于任意一个0 <= B < A,总存在一个X的后续局面Y,

使得sg(Y) = B。
(2) 假设sg(x[1]) xor sg(x[2]) xor … xor sg(x[n]) = A,不存在一个X的后续局面Y,使得sg(Y) = A。
下先证明(1):
假设M = A xor 
B,设M表示为二进制之后最高位的1为第k位。所以A的第k位为1,B的第k位为0。又因为A的第k位为1,

至少存在一个i,sg(x[i])的第k位也为1。那么一定有sg(x[i]) xor M < sg(x[i]),即一定通过某个操作使x[i]变为x[i’],

且sg(x[i’]) = sg(x[i]) xor M。那么:

sg(x[i’]) xor Other = sg(x[i]) xor M xor Other = M xor A = B
下证明(2):
若sg(X) = A,sg(Y) = A。不妨设我们改变的游戏为x[i],则X=x[1..n], Y=x[1…i’…n]。有sg(x[i]) = sg(x[i’]),

产生矛盾,所以sg(Y)不可能等于A。

 

5.阶梯博弈

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

若干个点( >=1 )移到前面去..最后没有点可以移动的人输..

阶梯博弈也是可以转化成尼姆博弈的.

把所有奇数阶梯看成N堆石子..做nim..把石子从奇数堆移动到偶数堆可以理解为拿走石子..就相当于几个

奇数堆的石子在做Nim..

6.Chomp!博弈(巧克力游戏)

 

 

有一个n*m的棋盘,每次可以取走一个方格并拿掉它右边和上面的所有方格。拿到左下角的格子(1,1)者输,如下图是8*3的

棋盘中拿掉(6,2)和(2,3)后的状态。

 

 

结论:答案是除了1*1的棋盘,对于其他大小的棋盘,先手总能赢。

 

分析:有一个很巧妙的证明可以保证先手存在必胜策略,可惜这个证明不是构造性的,也就是说没有给出先手怎么下才能赢。

 

证明如下:

如果后手能赢,也就是说后手有必胜策略,使得无论先手第一次取哪个石子,后手都能获得最后的胜利。那么现在假设先手

最右上角的石子(n,m),接下来后手通过某种取法使得自己进入必胜的局面。但事实上,先手在第一次取的时候就可以和

后手这次取的一样,进入必胜局面了,与假设矛盾。

 

 

 

巧克力游戏的变形:

 

约数游戏:有1~n个数字,两个人轮流选择一个数字,并把它和它的约数擦去。擦去最后一个数的人赢,问谁会获胜。

 

分析:类似巧克力游戏,得到结论就是无论n是几,都是先手必胜。

 

 

 

翻棋子游戏:

 

题意:一个棋盘上每个格子有一个棋子,每次操作可以随便选一个朝上的棋子(x,y),代表第i行第j列的棋子,选择一个形

如(x,b)或(a,y)(其中b < y,a < x)的棋子,然后把它和(x,y)一起翻转,无法操作的人输。

 

分析:把坐标为(x,y)的棋子看成大小分别为x和y的两堆石子,则本题转化为了经典的Nim游戏,如果难以把棋子看作石

子,可以先把Nim游戏中的一堆石子看成一个正整数,则Nim游戏中的每次操作是把其中一个正整数减小或者删除。

 

 

 

题目:

 

POJ1704 阶梯博弈变形

题意:

从左到右有一排石子,给出石子所在的位置。规定每个石子只能向左移动,且不能跨过前面的石子。最左边的石子最多只能移动

到1位置。每次选择一个石子按规则向左移动,问先手是否能赢。

 

思路:

首先,按照我上面说的,先来寻找必胜态和必败态。什么时候会是必败态,小范围的,当只有两个石子的时候,先手必胜的. 也就是说

这时候谁先移动前面那颗石子谁就输了,另一个人就一直移动后面那个石子贴着前面那个,一直这样下去必输.

那么如果棋盘上有多对石子怎么办,我们根据上面那个想象一下,每对石子的前一个和上一个石子的后一个的距离对结果无影响.

对于每对石子之间,什么时候能紧贴着,那就没法走了只能移动前面那个石子,这个过程可以看成是一堆石子取空了.一对点的距离就是

石子数量

所以我们对这些石子排序,从后往前每两个一对,如果是奇数第一个数自己一堆.

 

 

 
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
 
const int maxn = 1e3+10;
int n;
int a[maxn];
int main()
{
	int _;
	cin>>_;
	while(_--)
	{
		cin>>n;
		for(int i = 1;i <= n;++i)
		{
			cin>>a[i];
		}
		int ans = 0;
		sort(a+1,a+1+n);
		if(n %2 == 0)
		{
			for(int i = n;i >= 2;i -= 2)
			{
				ans ^= (a[i] - a[i-1] - 1);
			}
		}
		else
		{
			for(int i = n;i >= 1;i -= 2)
			ans ^= (a[i] - a[i -1] - 1);
		}
		if(ans != 0)
		puts("Georgia will win");
		else
		puts("Bob will win");
	}
	return 0;
}


阶梯博弈变形

 

HDU - 4315

 

题意:

有n个人爬山,山顶坐标为0,其他人按升序给出,不同的坐标只能容纳一个人(山顶不限),Alice和Bob轮流选择一个人让他移动任意步,

但不能越过前面的人,且不能和前面一个人在相同的位置。现在有一个人是king,给出king是哪个人(id),谁能将国王移动到山顶谁胜。

思路:

这个题和上面那个很像,首先考虑不存在国王的情况,全是普通人,那就是上面那种情况了.还是相邻一组,考虑奇偶。

但是现在有国王了,1.当国王在第一个,先手必胜.2.当数量为奇数,并且国王在第二个的时候需要把第一个的距离减1,因为他不能直接移走,否则

又成了国王在第一个的情况必胜.3.其余的情况就把国王当成普通人处理就好.

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
 
const int maxn = 1e3+10;
int n,k;
int a[maxn];
int main()
{
	while(cin>>n>>k)
	{
		for(int i = 1;i <= n;++i)
		{
			cin>>a[i];
		}
		if(k==1)
		{
			puts("Alice");
			continue;
		}
		int ans;
		ans = 0;
		if(n % 2 == 0 || k != 2)
		a[0] = -1;
		else
		a[0] = 0;
		for(int i = n;i >= 1;i -= 2)
		{
			ans ^= (a[i] - a[i-1] - 1);
		}
		if(ans != 0)
		puts("Alice");
		else
		puts("Bob");
	}
	return 0;
}

 

hihocoder1172

 

题意;

Alice和Bob这一次准备玩一个关于硬币的游戏:N枚硬币排成一列,有的正面朝上,有的背面朝上,从左到右依次编号为1..N。

现在两人轮流翻硬币,每次只能将一枚正面朝上的硬币翻过来,并且可以随自己的意愿,在一枚硬币翻转后决定要不要将该硬币左边

的任意一枚硬币也翻一次(正面翻到背面或背面翻到正面)。翻最后一枚正面向上的硬币的人获胜。同样的,这次游戏里面Alice仍然先

手,两人均采取最优的策略,对于给定的初始局面,Alice会获胜还是Bob会获胜?

思路:

首先,我们先将局面分解一下,每一次我们只考虑一枚硬币。
不妨设所有硬币全部背面朝上的局面为局面0
假设现在N枚硬币,只有第1枚是正面朝上的。该局面只能转化为全部硬币背面朝上的局面。我们假定该局面为 局面1,则局面1可以

转化为局面0。
假设只有第2枚是正面朝上的。该局面可以转化为:只有硬币1正面朝上;全部硬币背面朝上。我们假定该局面为 局面2,局面2可以

转化为局面1和局面0。
同理我们可以推定,第i枚硬币正面朝上的局面为局面i,局面i可以转化为局面0..i-1。

现在,我们考虑把给定的局面拆成单个硬币的局面集合,比如给定了{HHTHTTHT},其中H表示正面朝上,T表示背面朝上。那么就

是当前局面={局面1,局面2,局面4,局面7}。每一次我们可以改变其中个一个局面,当出现局面0时就从集合中删去。
这样一看是不是就变成了Nim游戏了?然而事实并没有那么简单。

进一步分析,若同时存在i,j(j<i)两枚硬币正面朝上。我们将这个局面拆成2个单一的局面:即局面i和局面j。
在反转i的时候我们考虑从局面i转移到局面j,那么我们会有两个局面j。
表示第j枚被反转了2次,也就是回到了背面朝上的状态。
那么我们得到这个游戏一个性质:当出现两个同样的局面时,等价于这两个局面合并变成了局面0。

这种情况在Nim游戏中是没有的,那么它会对Nim游戏的状态造成影响么?
我们想一想,在Nim游戏中,如果出现两个数量相同的堆时,比如A[i]=A[j]。在计算Nim游戏状态时我们采用的xor操作,xor有交换律

和结合律。则我们可以变成:
(A[i] xor A[j]) xor Other
因为A[i] = A[j],所以A[i] xor A[j] = 0。上式实际就是:
0 xor Other
也就是说在原Nim游戏中,若出现了两个数量相同的堆时,实际上这两堆已经不对总局面造成影响了,也就可以认为这两对合并为了

一个数量为0的堆。

到此,我们可以发现这个硬币游戏完全满足Nim游戏的规则,其解答也满足Nim游戏的性质,这题也就很简单的转化为了普通的Nim

游戏。在实际的博弈游戏中会发现很多都是可以转化为Nim游戏模型。如何正确的建立模型和转化游戏模型也就是解决博弈游戏一个

很重要的手段。


#include<bits/stdc++.h>
 
const int maxn = 1e4 + 10;
using namespace std;
char s[maxn];
int main()
{
	int n;
	int x;
	int ans = 0;
	cin>>n;
	scanf("%s",s);
	for(int i = 0;i < n ;i++)
	{
		if(s[i] == 'H')
		ans ^= (i+1);
	}
	if(ans == 0)
	puts("Bob");
	else
	puts("Alice");
	return 0;
 } 

hihocoder 1173

HDU - 5795

 

题意:

在这一次游戏中Alice和Bob决定在原来的Nim游戏上增加一条规则:每一次行动时,不仅可以选择一堆取走任意数量的石子(至少取1

颗,至多取出这一堆剩下的所有石子),还可以选择将一堆石子分成两堆石子,但并不取走石子。比如说有一堆石子为k个,当Alice或

者Bob行动时,可以将这一堆石子分成两堆,分别为x,y。满足x+y=k,x,y>0。那么增加了这一条规则后,在Alice总先手的情况下,请

你根据石子堆的情况判断是Alice会获胜还是Bob会获胜?

 

思路:

这两个题目基本一样,根据sg函数的定理:

对于多个单一游戏,X=x[1..n],每一次我们只能改变其中一个单一游戏的局面。则其总局面的sg值等于这些单一游戏的sg值

异或和。
即:
sg(X) = sg(x[1]) xor sg(x[2]) xor … xor sg(x[n])

我们只需要算单个的就好. 由于数据比较大,我们不可能把所有的sg函数值处理出来,所以打表找找规律.

 

 
#include<bits/stdc++.h>
 
const int maxn = 2e4+10;
using namespace std;
int n;
int a[111];
bool vis[maxn];
int sg[maxn];
void init()
{
	sg[0] = 0;
	for(int i = 1;i <= 100;i++)
	{
		for(int j = 1;j <= 100;j++) vis[j] = 0;
		for(int j = 1;j < i ;j++)
		{	
			vis[sg[j]] = 1;
			vis[sg[j] ^ sg[i - j]] = 1;
		}
		for(int j = 1;j <= 100;j++)
		{	
			sg[i] = j;
			if(!vis[j])
			{
			
				break;
			}
		}
	}
	for(int i = 1;i <= 100;i++)
	printf("%d %d\n",i,sg[i]);
	return ;
}
 
int main()
{
	//init();
	while(cin>>n)
	{
		int ans = 0;
		for(int i = 1;i <= n;i++)
		{
			int x;
			scanf("%d",&x);
			if(x % 4 == 0)
			{
				ans ^= (x - 1);
			}
			else if( (x+1) % 4 == 0)
			{
				ans ^= (x + 1);
			}
			else 
				ans ^= x; 
		}
		if(ans == 0)
		puts("Bob");
		else
		puts("Alice");
	}
	return 0;
}

 

 

 
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 2e4+10;
using namespace std;
int n;
ll a[111];
bool vis[maxn];
ll sg[maxn];
void init()
{
	sg[0] = 0;
	for(int i = 1;i <= 100;i++)
	{
		for(int j = 1;j <= 100;j++) vis[j] = 0;
		for(int j = 1;j < i ;j++)
			vis[sg[j]] = 1;	
		for(int j = 1;j < i;j++)
		 for(int k = 1;k < i - j;k++)
		 vis[sg[j] ^ sg[k] ^ sg[i-j-k]] = 1;
		for(int j = 1;j <= 100;j++)
		{	
			sg[i] = j;
			if(!vis[j])
			{
			
				break;
			}
		}
	}
	for(int i = 1;i <= 100;i++)
	printf("%d %d\n",i,sg[i]);
	return ;
}
 
int main()
{
//	init();
	int _;
	cin>>_;
	while(_--)
	{
		cin>>n;
		ll ans = 0;
		for(int i = 1;i <= n;i++)
		{
			ll x;
			scanf("%lld",&x);
			if(x % 8 == 0)
			{
				ans ^= (x - 1);
			}
			else if( (x+1) % 8 == 0)
			{
				ans ^= (x + 1);
			}
			else 
				ans ^= x; 
		}
		if(ans == 0)
		puts("Second player wins.");
		else
		puts("First player wins.");
	}
	return 0;
}

 

K倍动态减法游戏. 当k为2时,为斐波那契博弈.

 

这个东西比较高深啊...看了好久也只是懂了一点.

#include <stdio.h>

#include <iostream>

using namespace std;


long long a[3000000], b[3000000];

//a[] 所有的必败态的数

//b[] 保存 a[0...i] 组合能够构造的最大数字

int main (void)

{

int t;

scanf("%d", &t);

while(t --)

{

int m;

long long n;

scanf("%d %lld", &m, &n);

a[0] = b[0] = 1;

int i = 0, j = 0;

while(n > a[i])

{

i ++;

a[i] = b[i - 1] + 1;

while(a[j + 1] * m < a[i])

j++;

if(a[j] * m < a[i])

b[i] = b[j] + a[i];

else

b[i] = a[i];

}

long long ans;

if(n == a[i]) //a 数组存的全部是必败态的数

ans = (long long)n - i - 1;//因为n不能取 1 所以要减1

else

ans = (long long)n - i;//i是a的下标,也正好是比N小的不能取的数的个数 (包括1)

printf("%lld\n", ans);

}

return 0;

}


 

 

除法游戏:

 

题目:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2959

 

题意:有一个n*m的矩阵,每个元素均为2~10000之间的正整数。两个游戏者轮流操作。每次可以选一行中的1个或者多个大于1的整数,把

它们中的每个数都变成它的某个真因子,比如12可以变成1,2,3,4或者6,不能操作的输(换句话说,如果在谁操作之前,

矩阵中的所有数都是1,则他输)。

 

分析:考虑每个数的素因子个数,比如12包含3个素因子,则让一个数变成它的真因子等价于拿掉它的一个或者多个素因

子。这样,每行对应于一个石堆,每个数的每个素因子看成是一颗石子,则本题就和Nim游戏完全等价了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值