三大博弈博弈+斐波那契博弈+sg函数

acm博弈

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

–>要保持给对手留下(m+1)的倍数,就能最后获胜
(这个游戏还可以有一种变相的玩法:两个人轮流报数,每次至少报一个,最多报十个,谁能报到100者胜,下面这题就是这个意思)

例题

HDU4764-Stone

//巴什博弈:只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。
#include<bits/stdc++.h>
using namespace std;
int main(void)
{
	int i,n,k;
	while(~scanf("%d %d",&n,&k)&&n!=0||k!=0)
	{
		//题意:写一个 不小于 N 的数字的人将失去游戏
		if((n-1)%(k+1)==0)//K+1的倍数就是先手输 
		{
			printf("Jiang\n");
		}
		else
		printf("Tang\n");
	}
	return 0;
}

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

先手奇异局先手者–> 局势:(a,b)[a<b]
满足a = =int( (b-a)*g )则为奇异局
即每种奇异局势的第一个值总是等于当前局势的差值乘上1.618
证明:https://blog.csdn.net/qq_41311604/article/details/79980882

例题:
hud1527取石子游戏

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
int main(void)
{
    int a,b;
    while(~scanf("%d %d",&a,&b))
    {
        if(a>b)
        swap(a,b);
        double g=(sqrt(5.0)+1.0)/2.0;//
        if(a==int((b-a)*1.0*g))//奇异局势 
        printf("0\n");
        else
        printf("1\n"); 
    }
    return 0;
}

关于奇异局,我并没有找到具体的定义,可以理解为在采取最优决策时决定先手者胜负的局势

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

先手奇异局先手–>
奇异局(0,0,0)即(n,n,n)都可以转化为(0,n)->(0,0,0)
即一直异或,最后等于零就是奇异局先手胜
注意:要特判全是1的情况。[因为最后取完者输,否则不用洛谷P2197 【模板】nim游戏]

例题
HD1907-John

#include<bits/stdc++.h>
using namespace std;
//尼姆博奕(Nimm Game):有三堆各若干个物品,
//两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,
//最后取光者得胜 这里是输 
int main(void)
{
	int t,n,a[50];
	int x;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		int flag=0;
		x=0;//0异或任何数都等于本身 
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			x^=a[i];
			if(a[i]>1)
			flag=1;
		}
		if(flag==0)//全为1 为什么要特判因为最后取完者输 
		{
			if(x==1)//就是奇数 
			{
				printf("Brother\n");
			}
			else
		    	printf("John\n");
		}
		else
		{
			if(x==0)//奇异局 
			{
				printf("Brother\n"); 
			}
			else
			    printf("John\n");
		}
	}
	return 0;
}

(四)斐波那契博弈 : 有一堆物品,两人轮流取物品,先手最少取一个,至多无上限,但不能把物品取完,之后每次取的物品数不能超过上一次取的物品数的二倍且至少为一件,取走最后一件物品的人获胜。

结论是:先手胜当且仅当n不是斐波那契数(n为物品总数)

1.当n为斐波那契数时,n个物品可以分解为两堆,n=F[i]=F[k]+F[k-1] (i=k+1)
相当于先手面对两个斐波那契的小游戏,两个都是后手取完(因为F[i]<F[k]*1,故后手能一次取完大小为F[k]的石子堆)。

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

2.得当先手面临的不是斐波那契数时,把n分解为若干个斐波那契数由大到小组成,把n写成 n = f[a1]+f[a2]+……+f[ap]。(a1>a2>……>ap)
先手只需取组成n的最小斐波那契数f[ap]即可,因为组成n的各项是不连续的(就是说【f(a(p-1))>f(ap)*2】后手不能把f(a(p-1))一次取完),故在后手取完后面对斐波那契数,先手都能让对手处于斐波那契局势。
详细证明

例题
HDU2516-取石子游戏

//斐波那契博弈
#include<bits/stdc++.h>
using namespace std;
long long int f[65];//有人说55就够了
int main(void)
{
	int n,i,t;
	f[0]=1;f[1]=1;
	map<long long int,int>p;
	p[0]=1;p[1]=1;
	for(i=2;i<=60;i++){
	 f[i]=f[i-1]+f[i-2];
    p[f[i]]=1;
	}
	 while(~scanf("%d",&n)&&n!=0)
	 {
	 	if(p[n]!=1)
	 	printf("First win\n");
	 	else
	 	printf("Second win\n");
	 }
	 return 0;
}

SG

如果是要有一个每次抽取个数的集合就要用到SG函数了
hduP1848

本题有个步长序列f,每次取的数为斐波那契中的某项
sg()函数有两个性质:
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。
然后, sg(x) = mex(sg(x-f(i) )),其中 x>f(i) [f为斐波那契数列]是 x 后继状态,对所有x的后继状态的sg进行mex运算

#include<bits/stdc++.h>//nim就是尼姆博弈 
using namespace std;
#define N 20
int f[17];//取的方法
bool book[1010];//为了求mex 
int sg[1010];//sg打表 就是分治 把它们分成一个个小的游戏 
//就是模板题 
void sg_solve(int n)//N求解范围 f[]数组是可以每次取的值,n是s的长度。下标从0开始
{
 int i,j;
 for(i=1;i<=n;i++){
  memset(book,0,sizeof(book));
  for(j=0;f[j]<=i&&j<=N;j++)
  book[ sg[i-f[j] ] ]=1;
  for(j=0;;j++)
  if(!book[j])
  {
   sg[i]=j;
   break;
  }
 }
}
/*  首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于任意状态 x , 定义 SG(x) = mex(S),其中 S 是 x 后继状态的SG函数值的集合。如 x 有三个后继状态分别为 SG(a),SG(b),SG(c),那么SG(x) = mex{SG(a),SG(b),SG(c)}。 这样 集合S 的终态必然是空集,所以SG函数的终态为 SG(x) = 0,当且仅当 x 为必败点P时。
【实例】取石子问题
有1堆n个的石子,每次只能取{ 1, 3, 4 }个石子,先取完石子者胜利,那么各个数的SG值为多少?
SG[0]=0,f[]={1,3,4},
x=1 时,可以取走1 - f{1}个石子,剩余{0}个,所以 SG[1] = mex{ SG[0] }= mex{0} = 1;
x=2 时,可以取走2 - f{1}个石子,剩余{1}个,所以 SG[2] = mex{ SG[1] }= mex{1} = 0; 先手必败 
x=3 时,可以取走3 - f{1,3}个石子,剩余{2,0}个,所以 SG[3] = mex{SG[2],SG[0]} = mex{0,0} =1;
x=4 时,可以取走4-  f{1,3,4}个石子,剩余{3,1,0}个,所以 SG[4] = mex{SG[3],SG[1],SG[0]} = mex{1,1,0} = 2;
x=5 时,可以取走5 - f{1,3,4}个石子,剩余{4,2,1}个,所以SG[5] = mex{SG[4],SG[2],SG[1]} =mex{2,0,1} = 3;
以此类推.....
   x        0  1  2  3  4  5  6  7  8....
SG[x]    0  1  0  1  2  3  2  0  1....
sg=0;先手输 
*/
int main(void)
{
 int i,m,n,p;
 f[0]=f[1]=1;//存入每次可取的值
 for(i=2;i<=16;i++)
 f[i]=f[i-1]+f[i-2]; 
 while(~scanf("%d %d %d",&n,&m,&p))
 {
  if(n==0&&p==0&&m==0)
  break;
  sg_solve(1000);//n,m,p都小于等于1000 
  if(sg[n]^sg[m]^sg[p])
  printf("Fibo\n");
  else
  printf("Nacci\n");
 }
 return 0;
}

B站有视频讲解:https://www.bilibili.com/video/av48199226

mex()运算介绍
请移步到这里

SG定理就是一个组合游戏,若是由许多个小游戏,或者说小的部分组成的,那么大游戏的SG函数值就是所有的小游戏的函数值的异或和,当大游戏的SG函数值为0时,先手必败,反之先手必胜

看了视频可以大概了解SG的作用了,不懂就当结论记住吧
sg不过瘾?点击这里

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值