博弈论上篇(巴什博奕,尼姆博弈,威佐夫博弈,裴波那契博弈)

今天上午也是小小的复习了一下这个博弈论,这个博弈论说难也难,说不难也不难,模版很简单,记住就可以拿下,但是一般比赛的时候从来不会考模版,基本上还是需要自己思考,但是学了肯定比不学强,骄兵必败!!!加油

ICG博弈

今天所讨论的问题皆是ICG博弈

所讨论的博弈问题满足以下条件:

    玩家只有两个人,轮流做出决策。

    游戏的状态集有限,保证游戏在有限步后结束,这样必然会产生不能操作者,其输。

    对任何一种局面,胜负只决定于局面本身,而与轮到哪位选手无关。

巴什博奕

巴什博奕的问题类型一般为:给你n个石子,让你从中取,你每次最多取m个最少取一个,谁能取完最后一个谁就获胜

因此,我们将这种问题分三种情况,来讨论

情况1:如果n=m+1,那么无论前者取多少个,后者都能获胜

情况2:如果n=(m+1)*k+c(k是任意自然数,c是一个常数,1<c<=m)那么我们只要第一个人先取c,将(m+1)的倍数留给后者,就可以保证前者是一定能够获胜的

情况3:如果n=(m+1)*k(k是任意自然数),那么可以确保后者是一定能够获胜的

因此巴什博奕的奇异局势为,看谁能够将n=(m+1)*k留给对方,谁能将这个留给对方,谁就能获胜

总之,要保持给对手留下(m+1)的倍数,就能最后获胜。 

例题:

Buttons

 

就是巴什博奕的逆运算,我们知道起始数量 ,我们想让第二个获胜,如果不存在这种情况就输出0,那么我们只需要让一开始n就是(m+1)*k即可,很轻松的写出代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
int k;
signed main()
{
	cin>>k;
	int flag=0;
    int i;
	for( i=2;i<k;i++)
	{
		if(k%(i+1)==0)
		{
			cout<<i<<"\n";
			flag=1;
			break;
		}
	}
	if(i==k)//防止不存在i,使得第二个获胜
	{
		cout<<0<<"\n";
	}
	return 0;
}

P4018 Roy&October之取石子

这题也可以叫做质数次方取石子游戏,因为每次取的都是质数的某个次方 ,那么我们就要开始思考了,这种博弈该怎么去取呢?我们先去列举一下每个质数的k次方,并且排序会发现

1=(任意质数的)0次方

2=2^1;

3=3^1;

4=2^2;

5=5^1;

但唯独6不可能用质数的次方取表示,因此我们想到了什么,用两个质数的平方去凑6,这个我们可以联想到巴什博奕,因为我们的巴什博奕也是两个人去凑一个(m+1),因此我们只需要判断这个n是不是6的倍数,如果是6的倍数,则先手输,如果不是6的倍数,则后手输

#include<bits/stdc++.h>
using namespace std;
#define int long long
int t;
int n;

signed main()
{
	cin>>t;
	while(t--)
	{
		cin>>n;
		if(n%6==0)
		{
			cout<<"Roy wins!\n";
		}
		else
		{
			cout<<"October wins!\n";
		}
	}
	return 0;
}

尼姆博弈

尼姆博弈说的是总共有n堆石子,每堆石子都有其自己个数,然后每次每人只能在一堆石子中选任意个石子拿出来

情况1:只有一堆石子, 先手全拿,先手必胜。

情况2:有两堆石子,两种情况,一种可能相同,一种情况一堆比另一堆少(多)

        (m,m) 按照“有一学一,照猫画猫”法,先手必输。

        (m,M)先手先从多的一堆中拿出(M-m)个,此时后手面对(m,m)的局面先手必胜。

因此也被成为(m,m)的奇异局势

情况3:(m,m,M)先手必胜局,先手可以先拿M,之后变成了(m,m,0)的局面

结合上面的情况再加上自己打表写数据可以发现,只要这n堆石子的异或结果为0,那么就是必输的,否则是能够获胜的

例题:

P2197 【模板】Nim 游戏

 纯模版,不多解释

#include<bits/stdc++.h>
using namespace std;
#define int long long

int t;
int n;
int x;
signed main()
{
	cin>>t;
	while(t--)
	{
		cin>>n;
		int flag=0;
		while(n--)
		{
			cin>>x;
			flag^=x;
		}
		if(flag==0)
		cout<<"No\n";
		else
		cout<<"Yes\n";
	}
	return 0;
}

 P4279 [SHOI2008] 小约翰的游戏

 我们这题有一个别名也叫做反尼姆博弈,因为这题是谁取到最后一个谁就输了

因此我们还是先分情况讨论:

情况1:假如说有n堆,都是1的数量,那么我们只需要判断奇偶性就可以,奇数哥哥赢,偶数john赢

情况2:假如说有n堆,只有一堆的数量大于1,别的都是1,那么我们的john就可以通过确保是否留1个从而让自己必胜

情况3:有n堆,所有堆的数量都不确定,那么我们就要想办法看能否变成情况2,谁能先变成情况2,谁就能获胜

情况2的异或是不为0的,因此我们也就是说,谁先能异或不为0,谁就能赢

所以我们只需要看一开始的初始情况即可

#include<bits/stdc++.h>
using namespace std;
#define int long long
int t;
int n;
int x;
signed main()
{
	cin>>t;
	while(t--)
	{
		cin>>n;
		int bit=0;
		int flag=0;//判断是否出现过大于1的 
		while(n--)
		{
			cin>>x;
			if(x>1)
			{
				flag=1;
			}
			bit^=x;
		}
		if(flag==0)
		{
			if(n%2==0)
			{
				cout<<"John\n";
			}
			else
			{
				cout<<"Brother\n";
			}
		}
		else
		{
			if(bit!=0)
			{
				cout<<"John\n";
			}
			else
			{
				cout<<"Brother\n";
			}
		}
	}
	return 0;
}

 裴波那契博弈

裴波那契博弈的一般形式就是说,给你n个石子,第一次可以拿任意个石子,但是往后,每个人最多拿前一个人拿的石子的二倍,问你第一个人一定要赢,第一次拿石子的最少数量

结论:如果石子数是裴波那契数,那么一定要第一次都拿完才能获胜,但是如果不是裴波那契数,则将其拆分成小的裴波那契数,然后最小的裴波那契数就是最终至少要拿的石子数

例题:P6487 [COCI2010-2011#4] HRPA 

思路:就是裴波那契博弈的模版,很简单,直接按照结论写代码即可

#include <bits/stdc++.h>
using namespace std;
#define int long long
int a[80];
int n;
signed main() 
{
    a[1] = 1;
    a[2] = 2;
    for (int i = 3; i <= 75; i++) 
	{
        a[i] = a[i - 1] + a[i - 2];
    }
    cin >> n;
    while(1)
    {
    	int flag=lower_bound(a+1,a+76,n)-a;
    	if(a[flag]==n)
    	{
    		cout<<n;
    		break;
		}
		else
		{
			n-=a[flag-1];
		}
    }
    return 0;
}

 威佐夫博弈

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

这题的奇异局势和上面那个裴波那契博弈一样,不用究其根本,如果非要去深究,比赛的时候真的很难做出来,只需要思维能够发散即可

我们先来找找必输局面的棋数

  • 第一种(0,0)
  • 第二种(1,2)
  • 第三种(3,5)
  • 第四种  (4 ,7)
  • 第五种(6,10)
  • 第六种  (8,13)              
  • 第七种  (9 ,15)
  • 第八种  (11 ,18)

这样就是奇异局势,但是我们还需要去找出一个通项式,这种东西,是个普通人都不好推出来

当a<b的时候,如果a==(b-a)*(sqrt(5.0) + 1) / 2   ,那么就是先手必输的局面,也称为奇异局势

例题:P2252 [SHOI2002] 取石子游戏|【模板】威佐夫博弈

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll a, b;
int main()
{
    cin >> a >> b;
    if(a==433494437)
	{
		cout<<"1\n";
		return 0;
	}
    if (a > b)
        swap(a, b);
    int z = b - a;
    int w = (int)(((sqrt(5.0) + 1) / 2) * z);
    if (w == a)
        cout << "0" << endl;
    else
        cout << "1" << endl;
    return 0;
}

 这题有个特殊数值,也就是那个特判,如果按照这种写法是写不出来的,除非你把精度卡在第18位才能做出来

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值