博弈论小结

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

①博弈模型为两个人轮流决定的非合作博弈,即两个人轮流进行决策,并且每次都会采用最优策略。
②博弈模型必须是有限次可以完成的。
③对两个人的规则是公平的。

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

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

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

①将所有终止状态定义为P状态
②将所有可以一步到达P状态的点,定义为N状态
③如果存在某个状态,它的下一步都是N状态,定义为P状态
④循环②,③步骤

巴什博弈(Bash Game)

描述

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

判断条件

判断 n % ( m + 1 ) n\%(m+1) n%m+1,不为零时获胜。先手第一次取走 n % ( m + 1 ) n\%(m+1) n%m+1个物品,然后根据后手所取数目 k k k,再取走 ( m + 1 − k ) (m+1-k) m+1k个即可。

例题

hdu 1846。
http://acm.hdu.edu.cn/showproblem.php?pid=1846

#include
using namespace std;
inline int read()
{
	int f=1,num=0;
	char ch=getchar();
	while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
	while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
	return num*f;
}
int main()
{
	int c=read();
	while (c--)
	{
		int n=read(),m=read();
		if (n%(m+1))
			puts("first");
		else
			puts("second");
	}
	return 0;
}

威佐夫博弈(Wythoff Game)

描述

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

判断条件

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

例题

poj 1067
http://poj.org/problem?id=1067

//#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main()
{
	double t=(1+sqrt(5.0))/2.0;
	int a,b;
	while (scanf("%d%d",&a,&b)==2)
	{
		if (a>b)
			swap(a,b);
		int k=b-a;
		if (a==(int)(k*t))
			puts("0");
		else
			puts("1");
	}
	return 0;
}

看着这么长的头文件,,,,我有点虚。。

尼姆博弈(Nimm Game)

描述

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

判断条件

使用 ( A 1 , A 2 , A 3 ) (A1,A2,A3) (A1,A2,A3)来描述所处状态 a n s = A 1 x o r A 2 x o r A 3 ans=A1 xor A2 xor A3 ans=A1xorA2xorA3如果ans为零则为P状态。

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

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

例题

hdu 1850

http://acm.hdu.edu.cn/showproblem.php?pid=1850

#include
using namespace std;
inline int read()
{
	int f=1,num=0;
	char ch=getchar();
	while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
	while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
	return num*f;
}
//Nim组合博弈问题
const int maxn=1e6+10;
int m,n[maxn];
int main()
{
	while (scanf("%d",&m) &&m)
	{
		int count=0,ans=0;
		for (int i=1;i<=m;++i)
		{
			n[i]=read();
			ans^=n[i];
		}
		/*这里有个技巧,首先求出所有的异或值,
		如果想去除的话,只需要再次进行异或操作即可,
		因为x^x=0,执行两次等于没有执行。
		原来每次都需要进行一次计算,
		这样可使算法效率从O(n*n)缩小到了O(n)*/
		for (int i=1;i<=m;++i)
			if ((ans^n[i])<n[i])
				++count;
		printf("%d\n",count);
	}
	return 0;
}

hdu 1907,最后取走的为失败者p2000

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

http://acm.hdu.edu.cn/showproblem.php?pid=1907

//这个问题关键在最后取走的为失败者。
#include
using namespace std;
const int maxn=4747+10;
inline int read()
{
	int f=1,num=0;
	char ch=getchar();
	while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
	while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
	return num*f;
}
int a[maxn];
int main()
{
	int t=read();
	while (t--)
	{
		int n=read(),ans=0,sum=0;
		for (int i=1;i<=n;++i)
		{
			a[i]=read();
			ans^=a[i];
			sum+=a[i];
		}
		if (sum==n)
			printf("%s\n",sum%2?"Brother":"John");
		else
			printf("%s\n",ans?"John":"Brother");
	}
	return 0;
}

luogu 2197

https://www.luogu.org/problemnew/show/P2197

#include
using namespace std;
inline int read()
{
	int f=1,num=0;
	char ch=getchar();
	while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
	while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
	return num*f;
}
int main()
{
    int t=read();
    while (t--)
    {
        int n=read(),ans=0;
        for (int i=1;i<=n;++i)
		{
            int num=read();
            ans^=num;
        }
        if (!ans)
			puts("No");
        else
			puts("Yes");
    }
	return 0;
}

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.
根据定义,考虑以下三点:

1. 如果x是终止状态,那么g(x)=0。
2. 一个状态x,如果g(x)≠0,那么一定存在一个x的后继y,使得g(y)=0。
3. 3.一个状态x,如果g(x)=0,那么所有x的后继y,都有g(y)≠0。

这三句话表明,顶点x所代表的position是P-position当且仅当g(x)==0.
计算出所有情况的SG值,最后求异或
g ( G ) = g ( G 1 ) x o r g ( G 2 ) x o r g ( G 3 ) . . . . . . . . g(G)=g(G1) xor g(G2) xor g(G3)........ g(G)=g(G1)xorg(G2)xorg(G3)........ g ( G ) = = 0 g(G)==0 g(G)==0时为P状态。
定理2:
G = G 1 + G 2 + … + G n , G i G=G1+G2+…+Gn,Gi G=G1+G2++GnGi S G 函 数 SG函数 SG g i , i = 1 , 2 , … , n gi,i=1, 2, …, n gii=1,2,,n那么G的 S G 函 数 SG函数 SG g ( x 1 , x 2 , … , x n ) = g 1 ( x 1 ) + g 2 ( x 2 ) + … + g n ( x n ) g(x1, x2, …, xn)=g1(x1)+g2(x2)+…+gn(xn) g(x1,x2,,xn)=g1(x1)+g2(x2)++gn(xn)加法表示Nim和,即不进位的二进制加法
自己的理解:SG模型,为多种情况不同处理的并,每次都需要求一次g函数,处理可以随便进行

例题

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

http://acm.hdu.edu.cn/showproblem.php?pid=1847

//第一种解法,历编自己找到规律
#include
using namespace std;
int main()
{
	int n;
	while (scanf("%d",&n)!=EOF)
	{
		if (n%3==0)
			printf("Cici\n");
		else
			printf("Kiki\n");
	}
	return 0;
}
//第二种解法
#include
const int maxn=1e3+10;
int a[10],g[maxn];
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()	
{
	int n;
	for (int i=0;i<10;i++)
		a[i]=1<<i;
	memset(g,-1,sizeof(g));
	g[0]=0;
	while (scanf("%d",&n)==1)
	{
		if (dfs(n))
			puts("Kiki");
		else
			puts("Cici");
	}
	return 0;
}

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

http://acm.hdu.edu.cn/showproblem.php?pid=1536

#include
using namespace std;
const int maxm=105,maxn=10005;
inline int read()
{
	int f=1,num=0;
	char ch=getchar();
	while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
	while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
	return num*f;
}
int k,n;
int s[maxm],H[maxm],g[maxn];
bool h[maxn][maxm];//从②处可得知,二维数组的大小。
int dfs(int n)
{
	if (g[n]!=-1)
		return g[n];
	for (int i=1;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;
}
int main()
{
	while (1)
	{
		k=read();
		if (!k)	break;
		for (int i=1;i<=k;++i)
			s[i]=read();
		memset(g,-1,sizeof(g));
		memset(h,0,sizeof(h));
		g[0]=0;
		int m=read();
		while (m--)
		{
			int ans=0;
			n=read();
			for (int i=1;i<=n;++i)
			{
				scanf("%d",&H[i]);
				ans^=dfs(H[i]);
			}
			if (ans)
				printf("W");
			else
				printf("L");
		}
		printf("\n");
	}
	return 0;
}

hdu 1848,其实还是SG函数问题p2003

http://acm.hdu.edu.cn/showproblem.php?pid=1848

#include
using namespace std;
const int maxn=17;
inline int read()
{
	int f=1,num=0;
	char ch=getchar();
	while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
	while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
	return num*f;
}
int f[maxn],g[1005],num;
int fun(int n)
{
	if (g[n]!=-1)
		return g[n];
	bool h[maxn];
	memset(h,0,sizeof(h));
	int i;
	for (i=1;i<=num && f[i]<=n;++i)
		h[fun(n-f[i])]=1;
	for (i=0;h[i];++i);
	return g[n]=i;
}
int main()
{
	int i;
	f[1]=1,f[2]=2;
	for (i=3;f[i-1]<1000;++i)
		f[i]=f[i-1]+f[i-2];
	num=i-1;
	memset(g,-1,sizeof(g));
	while (1)
	{
		int m=read(),n=read(),p=read();
		if (!m && !n && !p)	break;
		int ans=fun(m)^fun(n)^fun(p);
		printf("%s\n",ans==0?"Nacci":"Fibo");
	}
	return 0;
}

补充:阶梯博弈( Staircase Nim )

描述

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

判断条件

对只需要对奇数号码的阶梯进行Nim运算即可。
先取者使状态到P状态,如果后面一个人也从奇数号码阶梯取石子,则按照Nim步骤操作即可,从奇数阶梯到偶数阶梯相当于取出石子扔掉。
若从偶数号码阶梯取石子,则先取者只需要将相同的石子从个该阶梯传到下面一个即可。
这里有详细介绍:http://blog.csdn.net/kk303/article/details/6692506

例题p2004

poj 1704
http://poj.org/problem?id=1704

//#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=1010;
inline int read()
{
	int f=1,num=0;
	char ch=getchar();
	while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
	while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
	return num*f;
}
int p[maxn];
int cmp(const void *a ,const void *b)
{
	return *(int *)a-*(int *)b;//从小到大排序,把a,b位置反过来就是从大到小
}
int main()
{
	int t=read();
	while (t--)
	{
		int n=read();
		p[0]=0;
		for (int i=1;i<=n;++i)
			p[i]=read();
		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;
		if (!ans)
			puts("Bob will win");
		else
			puts("Georgia will win");
	}
	return 0;
}

hdu 1517,寻找失败类自己找出规律来
http://acm.hdu.edu.cn/showproblem.php?pid=1517

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

推荐几个博客:

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 组合博弈知识汇总,这个博客里面有很多的补充

小结

这篇博客大多出自刘子祯学长的《博弈论小结》,在此特向刘子祯大佬表示无比的敬意。%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

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