初识博弈论

博弈论:

是二人或多人在平等的对局中各自利用对方的策略变换自己的对抗策略,达到取胜目标的理论。博弈论是研究互动决策的理论。博弈可以分析自己与对手的利弊关系,从而确立自己在博弈中的优势,因此有不少博弈理论,可以帮助对弈者分析局势,从而采取相应策略,最终达到取胜的目的。

ACM中的博弈论:

在算法竞赛中出现的博弈论的题目往往是ICG(Importial Compinatorial  Games 公平组合游戏)。一般有一下特征:

(1)有两名选手。

(2)操作交替进行,每一次都是在有限的合法集中选取一种。

(3)在任何情况下,合法操作只取决于情况本身,与选手无关。

(4)游戏失败条件为:某位选手不能再进行合法操作。

经典博弈问题:

巴什博奕(Bash Game):

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

分析:如果n=(m+1)*r+s,(r为任意自然数,s≤m),那么先取者要拿走s个物品,如果后取者拿走k(≤m)个,那么先取者再拿走m+1-k个,结果剩下(m+1)(r-1)个,以后保持这样的取法,那么先取者肯定获胜。总之,要保持给对手留下(m+1)的倍数,就能最后获胜。

巴什博奕的分析方法:

P点: 即必败点,某玩家位于此点,只要对方无失误,则必败。

N点: 即必胜点,某玩家位于此点,只要自己无失误,则必胜。

三个定理:

(1)所有终结点都是必败点P(上游戏中,轮到谁拿牌,还剩0张牌的时候,此人就输了,因为无牌可取)。

(2)所有一步能走到必败点P的就是N点。

(3)通过一步操作只能到N点的就是P点。

HDU 1846

#include<iostream>
#include<cstdio>
#include<cstring>
#define INF 0x3f3f3f3f
using namespace std;

int main()
{
	int t,n,m;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d %d",&n,&m);
		if(n%(m+1)==0)
			printf("second\n");
		else
			printf("first\n");
	}
	return 0;
}

Fibonacci’s Game(斐波那契博弈):

描述:有一堆个数为n的石子,游戏双方轮流取石子,满足:(1)先手不能在第一次把所有的石子取完;(2)之后每次可以取的石子数介于1到对手刚取的石子数的2倍之间(包含1和对手刚取的石子数的2倍)。

分析:

Zeckendorf定理(齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和。

我们可用数学归纳法证明斐波那契数列必败:

(1)当i=2时,先手只能取1颗,显然必败,结论成立。

(2)假设当i<=k时,结论成立。 则当i=k+1时,f[i] = f[k]+f[k-1]。

则我们可以把这一堆石子看成两堆,简称k堆和k-1堆。(一定可以看成两堆,因为假如先手第一次取的石子数大于或等于f[k-1],则后手可以直接取完f[k],因为f[k] < 2*f[k-1])

那么先手只能取<f[k-1]个石子,由归纳可知,必定是后手取到最后一个石子。那么对于剩下的第k堆,先手取不完所有的石子,同理可知:必定是后手取到最后一个石子。即i=k+1时,结论依然成立。

当n不是斐波那契数的时候,可对n进行分解:

n = f[a1]+f[a2]+……+f[ap]。(a1>a2>……>ap)

我们令先手先取完f[ap],即最小的这一堆。由于各个f之间不连续,则a(p-1) > ap  + 1,则有f[a(p-1)] > 2*f[ap]。即后手只能取f[a(p-1)]这一堆,且不能一次取完。因此先手必胜。

HDU 2516

#include<iostream>
#include<cstdio>
#include<cstring>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;

ll f[105];

int main()
{
	f[0]=f[1]=1;
	for(int i=2;i<=45;i++)
		f[i]=f[i-1]+f[i-2];
	int n;
	while(~scanf("%d",&n)&&n)
	{
		if(*lower_bound(f,f+46,n)==n)
			printf("Second win\n");
		else
			printf("First win\n");
	}
	return 0;
}

威佐夫博奕(Wythoff Game):

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

分析:

这种情况下是颇为复杂的。我们用(ak,bk)(ak ≤ bk ,k=0,1,2,…,n)表示两堆物品的数量并称其为局势,如果甲面对(0,0),那么甲已经输了,这种局势我们称为奇异局势。前几个奇异局势是:(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)。

可以看出,a0=b0=0,ak是未在前面出现过的最小自然数,而 bk= ak + k,奇异局势有如下三条性质:

(1)任何自然数都包含在一个且仅有一个奇异局势中。

由于ak是未在前面出现过的最小自然数,所以有ak > ak-1 ,而 bk= ak + k > ak-1 + k-1 = bk-1 > ak-1 。所以性质1成立。

(2)任意操作都可将奇异局势变为非奇异局势。

事实上,若只改变奇异局势(ak,bk)的某一个分量,那么另一个分量不可能在其他奇异局势中,所以必然是非奇异局势。如果使(ak,bk)的两个分量同时减少,则由于其差不变,且不可能是其他奇异局势的差,因此也是非奇异局势。

(3)采用适当的方法,可以将非奇异局势变为奇异局势。  

 从如上性质可知,两个人如果都采用正确操作,那么面对非奇异局势,先拿者必胜;反之,则后拿者取胜。

那么任给一个局势(a,b),怎样判断它是不是奇异局势呢?我们有如下公式:

  ak =[k(1+√5)/2],bk= ak + k  (k=0,1,2,…,n 方括号表示取整函数)

即:[(bk-ak)*(1+√5)/2]=ak。

HDU 1527

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;

int main()
{
	int a,b;
	double dis=(1+sqrt(5))/2;
	double temp;
	while(~scanf("%d %d",&a,&b))
	{
		if(a>b)
			swap(a,b);
		temp=b-a;
		if((int)(temp*dis)==a)
			printf("0\n");
		else
			printf("1\n");
	}
	return 0;
}

尼姆博奕(Nimm Game):

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

分析:将n堆物品数量全部异或 : 为零则必败,否则必胜(先手)。(证明可戳百度百科:尼姆博奕

SG(Sprague-Grundy) 函数:

懒得写了,感觉自己理解也不够深刻- -

百部百科:SG函数

推荐博客:SG函数详解

反SG博弈:

(一般的问题是 最后一个没有选择的人输 反SG博弈就是最后一个没有选择的人赢 即做了最后一步操作的人输)

如果定义所有子游戏的SG值为0时游戏结束,先手必胜的条件:

(1)游戏的SG值为0且所有子游戏SG值均不超过1。

(2)游戏的SG值不为0且至少一个子游戏SG值超过1。


 

猜你喜欢

转载自blog.csdn.net/xiji333/article/details/89492952