博弈论小结

博弈论,今天算是告一段落了。

首先来了解一下什么事博弈模型:

①博弈模型为两个人轮流决定的非合作博弈,即两个人轮流进行决策,并且每次都会采用最优策略。

②博弈模型必须是有限布可以完成的。

③对两个人的规则是公平的。

为了方便理解这个模型我们来定义两个状态:

P状态(必败态)前一个选手(Previous player)将取胜的位置称为必败点。 

N状态(必胜态)一个选手(Next player)将取胜的位置称为必胜点。

博弈类型题的一般解法步骤:

①将所有终止状态定义为P状态

②将所有可以一步到达P状态的点,定义为N状态

③如果存在某个状态,它的下一步都是N状态,定义为P状态

④循环②,③步骤

首先来看四个类型的题目

一)巴什博奕(Bash Game):只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。

判断条件:n%(m+1),不为零时获胜。第一步取走n%(m+1)个,然后根据第二个人所取数目k,跟着他取走(m+1-k)个即可。

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

判断条件:使用(ak,bk)来表示两个堆里面的物品数,则P状态满足:ak=k*[(1+√5)/2],bk=ak+k,(k=0,1,2,3......);

三)尼姆博奕(Nimm Game)简称Nim模型:有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

判断条件:使用(a1,a2,a3)来描述所处状态,ans=a1^a2^a3,如果ans为零则为P状态。

定理1:Nim游戏的一个状态(x1, x2, x3) 是P状态,当且仅当x1+x2+x3=0。多与三个状态一样满足该定理

定理的证明自己网上找。

自己的理解:Nim模型,为多种情况相同处理的并,而且处理只能是取走任意颗石子

四)Sprague-Grundy函数(简称SG函数):有n堆石子,每次可以从第1堆石子里取1颗、2颗或3颗,可以从第2堆石子里取奇数颗,可以从第3堆及以后石子里取任意颗,最后取光着得胜。

首先来了解一下什么为SG函数,对于一个递增有界的图G(X,F)来说,SG函数g,是定义在X上的函数,函数值是非负整数,使得g(x)的值等于所有x的后继的SG函数中没有出现的最小非负整数。对于递增有界的图,SG函数是唯一的、有界的。所有的终止状态x,因为F(x)是空集,所以g(x)=0.

根据定义,考虑以下三点:
如果x是终止状态,那么g(x)=0。
一个状态x,如果g(x)≠0,那么一定存在一个x的后继y,使得g(y)=0。
一个状态x,如果g(x)=0,那么所有x的后继y,都有g(y)≠0。

这三句话表明,顶点x所代表的position是P-position当且仅当g(x)==0.

计算出所有情况的SG值,最后求异或g(G)=g(G1)^g(G2)^g(G3)........,当g(G)==0时为P状态。

定理2:

设G=G1+G2+…+Gn,Gi的SG函数是gi,i=1, 2, …, n。那么G的SG函数g(x1, x2, …, xn)=g1(x1)+g2(x2)+…+gn(xn),加法表示Nim和,即不进位的二进制加法。

自己的理解:SG模型,为多种情况不同处理的并,每次都需要求一次g函数,处理可以随便进行

5)补充:阶梯博弈n个阶梯,阶梯上有一些石子,要求将阶梯上的石子都放到地上,操作只能是从第i个阶梯取若干个石子到第i-1上

判断条件:对只需要对奇数号码的阶梯进行Nim运算即可。

先取者使状态到P状态,如果后面一个人也从奇数号码阶梯取石子,则按照Nim步骤操作即可,从奇数阶梯到偶数阶梯相当于取出石子扔掉。若从偶数号码阶梯取石子,则先取者只需要将相同的石子从个该阶梯传到下面一个即可。

这里有详细介绍:http://blog.csdn.net/kk303/article/details/6692506

推荐几个博客:

http://www.cnblogs.com/Knuth/archive/2009/09/05/1561005.html,寻找必败态——一类博弈问题的快速解法

这其实也是每类博弈题最先考虑的问题,或者突破点所在。

http://www.cnblogs.com/Knuth/archive/2009/09/05/1561002.html,Game theory初步,对两个定理有详细的证明

http://www.cnblogs.com/Knuth/archive/2009/09/05/1561008.html,Nim游戏

http://www.cnblogs.com/Knuth/archive/2009/09/05/1561007.html,Sprague-Grundy函数

http://www.cnblogs.com/tanky_woo/archive/2010/08/20/1804464.html,组合博弈知识汇总,这个博客里面有很多的补充

下面是一些不错的题型:

hdoj1846,题型①

//AC,最简单的博弈题
#include <cstdio>
int main()
{
	//freopen("f://data.in","r",stdin);
	int C,n,m;
	scanf("%d",&C);
	while(C--)
	{
		scanf("%d %d",&n,&m);
		if(n%(m+1))
			printf("first\n");
		else
			printf("second\n");
	}
	return 0;
}

poj1067,题型②

//取石子游戏---威佐夫博奕(Wythoff Game)
#include <cstdio>
#include <cmath>
int main()
{
	double t=(1+sqrt(5.0))/2.0;
	int a,b;
	while(scanf("%d%d",&a,&b)==2)
	{
		if(a>b)
		{
			int temp=a;
			a=b;
			b=temp;
		}
		int k=b-a;
		if(a==(int)(k*t))
			printf("0\n");
		else
			printf("1\n");
	}
	return 0;
}


hdoj1850,Nim模型

//Nim组合博弈问题
#include <cstdio>
const int nMax=1000000+10;
int M,N[nMax];
int main()
{
	//freopen("f://data.in","r",stdin);
	while(scanf("%d",&M) && M)
	{
		int count=0,ans=0;
		for(int i=0;i<M;i++)
		{
			scanf("%d",&N[i]);
			ans^=N[i];
		}
		/*这里有个技巧,首先求出所有的异或值,如果想去除的话,只需要再次进行异或操作即可,因为x^x=0,执行两次等于没有执行。原来每次都需要进行一次计算,这样可使算法效率从O(n*n)缩小到了O(n)*/
		for(int i=0;i<M;i++)
		{
			if((ans^N[i])<N[i])
				count++;
		}
		printf("%d\n",count);
	}
	return 0;
}

hdoj1907,最后取走的为失败者

这里涉及到一些不同的东西。直接写结论:必败态中有一种情况需要单独判断,奇数个孤单堆(即奇数个堆石子数为1)为必败态。还有一点是,两人在后面取石子的方式会有所不同,需要注意,详细解释在http://www.cnblogs.com/tanky_woo/archive/2010/08/20/1804464.html里面有。当超过1颗石子的堆数大于1的时候,按照Nim的方法走。直到超过1颗石子的堆数等于1,这时将这堆石子全部取掉或剩1颗,保证非空(剩下1颗石子)的堆数为奇数。

//这个问题关键在最后取走的为失败者。
#include <cstdio>
int main()
{
	//freopen("f://data.in","r",stdin);
	const int nMax=4747+10;
	int T,N,A[nMax];
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&N);
		int ans=0;
		int sum=0;
		for(int i=0;i<N;i++)
		{
			scanf("%d",&A[i]);
			ans^=A[i];
			sum+=A[i];
		}
		if(sum==N)
		{
			printf("%s\n",sum%2?"Brother":"John");
		}
		else
		{
			if(ans)
				printf("John\n");
			else
				printf("Brother\n");
		}
	}
	return 0;
}

pdoj1847,简单SG问题,两种解法,第二种更普及一些:

//第一种解法,历编自己找到规律
#include <cstdio>
int main()
{
	int n;
	while(scanf("%d",&n)!=EOF)
	{
		if(n%3==0)
			printf("Cici\n");
		else
			printf("Kiki\n");
	}
	return 0;
}

//第二种解法

#include <cstdio>
#include <cstring>
const int nMax=1000+10;
int a[10];
int g[nMax];
void init()
{
	for(int i=0;i<10;i++)
		a[i]=1<<i;
	memset(g,-1,sizeof(g));
	g[0]=0;
}
int dfs(int n)
{
	if(g[n]!=-1) return g[n];
	bool h[15];//从①处可得知数组的大小
	memset(h,0,sizeof(h));
	int i;
	for(i=0;i<10 && n-a[i]>=0;i++)
		h[dfs(n-a[i])]=1;//①
	for(i=0;h[i];i++);
	return g[n]=i;
}
int main()	
{
	//freopen("f://data.in","r",stdin);
	int n;
	init();
	while(scanf("%d",&n)==1)
	{
		if(dfs(n))
			printf("Kiki\n");
		else
			printf("Cici\n");
	}
	return 0;
}

hdoj1536,再来一个SG函数问题加深一下印象

#include <cstdio>
#include <cstring>
const int mMax=105,nMax=10005;
int k,m,n;
int S[mMax],H[mMax];
int g[nMax];
bool h[nMax][mMax];//从②处可得知,二维数组的大小。
void init()
{
	for(int i=0;i<k;i++)
		scanf("%d",&S[i]);
	memset(g,-1,sizeof(g));
	memset(h,0,sizeof(h));
	g[0]=0;
}
int dfs(int n)
{
	if(g[n]!=-1)
		return g[n];
	for(int i=0;i<k;i++)
		if(S[i]<=n)//①因为S并不一定是从小到大排序好的,所以只能用if语句进行判断。
			//纠错时,不要去找语法,语法不会有错,要去发现算法,哪一步出现了问题。或许是某个细节没有注意。
			h[n][dfs(n-S[i])]=1;//②
	int i;
	for(i=0;h[n][i];i++);
	return g[n]=i;
}
void solve()
{
	scanf("%d",&m);
	while(m--)
	{
		int ans=0;
		scanf("%d",&n);
		for(int i=0;i<n;i++)
		{
			scanf("%d",&H[i]);
			ans^=dfs(H[i]);
		}
		if(ans)
			printf("W");
		else
			printf("L");
	}
	printf("\n");
}
int main()
{
	//freopen("f://data.in","r",stdin);
	for(;;)
	{
		scanf("%d",&k);
		if(!k)
			break;
		init();
		solve();
	}
	return 0;
}

hdoj1848,其实还是SG函数问题

#include <cstdio>
#include <cstring>
const int nMax=17;
int F[nMax],N;
int g[1005];
void init()
{
	int i;
	F[1]=1;
	F[2]=2;
	for(i=3;F[i-1]<1000;i++)
		F[i]=F[i-1]+F[i-2];
	N=i-1;
	memset(g,-1,sizeof(g));
}
int f(int n)
{
	if(g[n]!=-1) return g[n];
	bool h[nMax];
	memset(h,0,sizeof(h));
	int i;
	for(i=1;i<=N && F[i]<=n;i++)
		h[f(n-F[i])]=1;
	for(i=0;h[i];i++);
	return g[n]=i;
}
int main()
{
	//freopen("f://data.in","r",stdin);
	int m,n,p;
	init();
	for(;;)
	{
		scanf("%d%d%d",&m,&n,&p);
		if(m==0 && n==0 && p==0)
			break;
		int ans=f(m)^f(n)^f(p);
		printf("%s\n",ans==0?"Nacci":"Fibo");
	}
	return 0;
}

poj1704,阶梯博弈

/*
思路:阶梯博弈
建模:将两个棋子之间的位置数看做石子堆,棋子向左移动看做从左侧石子堆向右侧石子堆转移。
所以该题可转换成阶梯博弈模型进行求解。
*/
#include <cstdio>
#include <cstdlib>
const int nMax=1010;
int T,N,P[nMax];
int cmp(const void *a,const void *b)
{
	int *pa=(int *)a;
	int *pb=(int *)b;
	return *pa-*pb;
}
int main()
{
	//freopen("f://data.in","r",stdin);
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&N);
		P[0]=0;
		for(int i=1;i<=N;i++)
			scanf("%d",&P[i]);
		qsort(P,N+1,sizeof(P[0]),cmp);
		int ans=0;
		for(int i=N;i>=1;i-=2)
		{
				ans^=P[i]-P[i-1]-1;
		}
		printf("%s\n",ans==0?"Bob will win":"Georgia will win");
	}
}

hdoj1517,寻找失败类自己找出规律来

/*
思路:寻找失败类
这道题是自己做出来的好兴奋啊,原来WA,
通过测试特殊数据找出了程序细节处理上的错误
*/
#include <cstdio>
int op(long long n)
{
	if(n%18==0)
		return n/18;
	else
		return n/18+1;
}
int main()
{
	//freopen("f://data.in","r",stdin);
	long long n;//使用double类型也可通过
	while(scanf("%lld",&n)==1)
	{
		/*if(n==1)
		{
			printf("Stan wins.\n");
			continue;
		}
		while(n>18) n=op(n);
		if(n>1 && n<=9)
			printf("Stan wins.\n");
		else
			printf("Ollie wins.\n");*/
		//或者
		while(n>18) n=op(n);//因为这里在n=18时候会停止循环,边界处理没有明确,1<=n<=18;
		if(n<=9)
			printf("Stan wins.\n");
		else
			printf("Ollie wins.\n");
	}
	return 0;
}


你可能感兴趣的:(博弈论小结)