【算法与数据结构】—— 博弈论(高阶篇之SG博弈)

博弈论之SG博弈

SG博弈的命名源于SG函数和SG定理,而SG函数的出现则来自于一个简单的取石子游戏:
有1堆n个的石子,每次只能取{1,3,4}个石子,先取完石子者胜利,判断对于不同的n,先手能否取胜?

分析:
这个游戏和巴什博弈的不同在于:
SG博弈中取东西是无规则不连续的,而巴什博弈中取东西则是连续的
因此在讨论SG博弈时要复杂很多,在解答SG博弈类题目时我们需要用到SG函数

SG函数:
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0
接下来看一下在1堆n个石子中,随n变化时SG函数值的变化(注意初始情况下可取石子为f[]={1,3,4})
当 n=0 时,先手无法取走任何石子,故SG[0]=0
当 n=1 时,先手可取f{1}个石子,剩余{0}个,所以 SG[1] = mex{ SG[0] } = mex{0} = 1
当 n=2 时,先手可取f{1}个石子,剩余{1}个,所以 SG[2] = mex{ SG[1] } = mex{1} = 0
当 n=3 时,先手可取f{1,3}个石子,剩余{2,0}个,所以 SG[3] = mex{SG[2],SG[0]} = mex{0,0} = 1
当n=4 时,先手可取f{1,3,4}个石子,剩余{3,1,0}个,所以 SG[4] = mex{SG[3],SG[1],SG[0]} = mex{1,1,0} = 2
当 n=5 时,先手可取f{1,3,4}个石子,剩余{4,2,1}个,所以SG[5] = mex{SG[4],SG[2],SG[1]} =mex{2,0,1} = 3
以此类推…
从上面可以看出求SG函数的过程如下:
1.首先定义SG[0]=0,且设可取石子的集合为f={ x1,x2,…,xn }(如上面的f={1,3,4})
2.对于输入的n,求出集合 sg = n-f(要求集合sg中均为非负数,如上面n=3时,得到的sg={2,0})
3.SG[i]=mex{sg}
根据这样的流程,最终可得到以下数据:

n 0 1 2 3 4 5 6 7 8 ……
SG[n] 0 1 0 1 2 3 2 0 1 ……

上表已经将SG函数的具体值求了出来,现在的问题是,得到的SG函数有什么用呢?
实际上SG函数为我们指示了先手在某个给定局势(即给定n)时,其能否取得最终的胜利(假设双方都采用最佳取法):对于某个给定的i,若SG[i]=0则表示先手必败,反之必胜



---经典题型---



HDU1847 Good Luck in CET-4 Everybody!
问题描述
大学英语四级考试就要来临了,你是不是在紧张的复习?也许紧张得连短学期的ACM都没工夫练习了,反正我知道的Kiki和Cici都是如此。当然,作为在考场浸润了十几载的当代大学生,Kiki和Cici更懂得考前的放松,所谓“张弛有道”就是这个意思。这不,Kiki和Cici在每天晚上休息之前都要玩一会儿扑克牌以放松神经。
“升级”?“双扣”?“红五”?还是“斗地主”?
当然都不是!那多俗啊~
作为计算机学院的学生,Kiki和Cici打牌的时候可没忘记专业,她们打牌的规则是这样的:
1、 总共n张牌;
2、 双方轮流抓牌;
3、 每人每次抓牌的个数只能是2的幂次(即:1,2,4,8,16…)
4、 抓完牌,胜负结果也出来了:最后抓完牌的人为胜者;
假设Kiki和Cici都是足够聪明(其实不用假设,哪有不聪明的学生~),并且每次都是Kiki先抓牌,请问谁能赢呢?
当然,打牌无论谁赢都问题不大,重要的是马上到来的CET-4能有好的状态。

Good luck in CET-4 everybody!

输入数据
输入数据包含多个测试用例,每个测试用例占一行,包含一个整数n(1<=n<=1000)。

输出数据
如果Kiki能赢的话,请输出“Kiki”,否则请输出“Cici”,每个实例的输出占一行。

样例输入
1
3

样例输出
Kiki
Cici


前面在学习巴什博弈的时候就遇到了这道题,当时是找到了一个规律:当n是3的倍数时先手必败
现在我们用SG博弈的方法再来解答本题,由于前面已经对SG博弈进行了详细的分析,这里就不再赘述
下面直接给出本题的完整代码:

#include<iostream>
#include<cstring>
using namespace std;

const int N=1005;
int SG[N],f[N];
bool vis[N];

int mex(bool vis[])	//定义mex函数 
{
	for(int i=0;i<N;i++)
		if(!vis[i]) return i;
}
void create_f()		//构建f集合 
{
	f[0]=1;
	for(int i=1;f[i-1]<N;i++)
		f[i]=f[i-1]*2;
}
void create_sg()	//构建sg函数 
{
	SG[0]=0;
	for(int i=1;i<N;i++)
	{
		memset(vis,0,sizeof(vis));
		for(int j=0;f[j]<=i;j++)
			vis[SG[i-f[j]]]=true;
		SG[i]=mex(vis);
	}
}

int main()
{
	create_f();
	create_sg();
	int n; 
	while(cin>>n)
	{
		if(SG[n]) cout<<"Kiki"<<endl;
		else cout<<"Cici"<<endl; 
	}
	return 0;
}
发布了39 篇原创文章 · 获赞 75 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/the_ZED/article/details/104381050