数学知识——博弈论

1. NIM游戏定义

给定N堆物品,第i堆物品有Ai个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但不能不取。取走最后一件物品者获胜。两人都采取最优策略,问先手是否必胜。

我们把这种游戏称为NIM博弈。把游戏过程中面临的状态称为局面。整局游戏第一个行动的称为先手,第二个行动的称为后手。若在某一局面下无论采取何种行动,都会输掉游戏,则称该局面必败。
所谓采取最优策略是指,若在某一局面下存在某种行动,使得行动后对面面临必败局面,则优先采取该行动。同时,这样的局面被称为必胜。我们讨论的博弈问题一般都只考虑理想情况,即两人均无失误,都采取最优策略行动时游戏的结果。
NIM博弈不存在平局,只有先手必胜和先手必败两种情况。

2. 公平组合游戏ICG
若一个游戏满足:
1)由两名玩家交替行动;
2)在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关;
3)不能行动的玩家判负;
则称该游戏为一个公平组合游戏。
NIM博弈属于公平组合游戏,但城建的棋类游戏,比如围棋,就不是公平组合游戏。因为围棋交战双方分别只能落黑子和白子,胜负判定也比较复杂,不满足条件2和条件3。

3. 有向图游戏
给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。
**任何一个公平组合游戏都可以转化为有向图游戏。**具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。

定理: NIM博弈先手必胜,当且仅当 A1 ^ A2 ^ … ^ An != 0
证明:因为最终失败状态是0^0 ^ 0 ^ … ^ = 0;
可证①当A1 ^ A2 ^ … ^ An = x != 0时在Ai中拿走Ai - Ai^x个石子可以使状态变为A1 ^ A2 ^ … Ai ^ x … ^ An = 0 →x ^ x = 0
②当A1 ^ A2 ^ … ^ An = 0时不管取任何一堆都会把状态变成A1 ^ A2 ^ … ^ An = 0 ,假设取了一堆后状态变成不为零,即:A1 ^ A2 ^ … Ai… ^ An = x将此式与A1 ^ A2 ^ … ^ An = x两边异或运算,得Ai ^ A* i = 0即Ai = A* i 这与开始的假设相矛盾,所以假设不成立

4. 例题:Nim游戏

给定n堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式
第一行包含整数n。

第二行包含n个数字,其中第 i 个数字表示第 i 堆石子的数量。

输出格式
如果先手方必胜,则输出“Yes”。

否则,输出“No”。

数据范围
1≤n≤105,
1≤每堆石子数≤109
输入样例
2
2 3
输出样例:
Yes

#include<iostream>
#include<algorithm>

using namespace std;

int main()
{
	int n;
	cin >> n;

	int res = 0;
	// 把所有的堆的数量异或运算
	while(n--)
	{
		int x;
		scanf("%d", &x);

		res ^= x;
	}
	// 根据定理推论,只要初始状态为不为零就是先手必胜,否则先手必败
	if(res) printf("Yes\n");	
	else printf("No\n");
	return 0;
}	

5. 从单个有向图游戏到多个有向图游戏的和
Mex运算
设S表示一个非负整数集合。定义mex(S)为求出不属于集合S的最小非负整数的运算,即:
mex(S) = min{x}, x属于自然数,且x不属于S

SG函数
在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1, y2, …, yk,定义SG(x)为x的后继节点y1, y2, …, yk 的SG函数值构成的集合再执行mex(S)运算的结果,即:
SG(x) = mex({SG(y1), SG(y2), …, SG(yk)})
特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)。

思路:把单个有向图的节点对应相应的SG函数,然后把所有的图的根节点看成一个NIM游戏。

证明:对应于NIM游戏的证明,最终失败状态时SG(x1)^ SG(x2) ^ …^ SG(xn) = 0
可证①当SG(x1)^ SG(x2) ^ …^ SG(xn) = x != 0时,因为SG(xi) ^ x < SG(xi), 所以SG(xi)可以变为SG(xi) ^ x → SG(x1)^ SG(x2) ^ …SG(xi) ^ x^ …^ SG(xn) = 0 →x ^ x = 0
②当SG(x1)^ SG(x2) ^ …^ SG(xn) != 0时不管取任何一堆都会把状态变成SG(x1)^ SG(x2) ^ …^ SG(xn) = 0 ,假设取了一堆后状态变成不为零,即:SG(xi) ^ x → SG(x1)^ SG(x2) ^ …SG(xi) ^ x^ …^ SG(xn) = x将此式与SG(x1)^ SG(x2) ^ …^ SG(xn) = x两边异或运算,得SG(xi) ^ SG(xi)* i = 0即SG(xi) =SG(xi)* i 这与SG函数的性质相矛盾,所以假设不成立

6.例题:集合-Nim游戏
给定n堆石子以及一个由k个不同正整数构成的数字集合S。

现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合S,最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式
第一行包含整数k,表示数字集合S中数字的个数。

第二行包含k个整数,其中第i个整数表示数字集合S中的第i个数si

第三行包含整数n。

第四行包含n个整数,其中第i个整数表示第i堆石子的数量hi

输出格式
如果先手方必胜,则输出“Yes”。

否则,输出“No”。

数据范围
1≤n,k≤100,
1≤si,hi≤10000
输入样例:
2
2 5
3
2 4 7
输出样例:
Yes

#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>

using namespace std;

const int N = 110, M = 10010;

int n, m;
int s[N], f[M]; // s[N]中保存可以取的石子的数量,f[M]中储存每个节点对应的SG函数的值

// SG函数,计算每个节点对应SG函数的值
int sg(int x)
{
    if (f[x] != -1) return f[x];  //如果此数量的SG函数的值之前计算过直接返回

    unordered_set<int> S;  // 创建一个哈希表,储存对应节点的图
    //枚举所有可取石子的情况,看当前x可以走到的所有状态并把对应的SG(x)插入哈希表
    for (int i = 0; i < m; i ++ )
    {
        int sum = s[i];
        if (x >= sum) S.insert(sg(x - sum));
    }

	// 从0开始枚举,最小的未在哈希表中出现过的数(状态)即为SG(x)
    for (int i = 0; ; i ++ )
        if (!S.count(i))
            return f[x] = i;
}


int main()
{
    cin >> m;
    for (int i = 0; i < m; i ++ ) cin >> s[i];
    cin >> n;

    memset(f, -1, sizeof f);
	
	//类似于NIM游戏只是把每一个x换成了SG(x)
    int res = 0;
    for (int i = 0; i < n; i ++ )
    {
        int x;
        cin >> x;
        res ^= sg(x);
    }

    if (res) puts("Yes");
    else puts("No");

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值