博弈论(NIM游戏、Mex运算、SG函数、有向图游戏)

NIM游戏
    给定N堆物品,第i堆物品有Ai个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但不能不取。取走最后一件物品者获胜。两人都采取最优策略,问先手是否必胜。
    
    我们把这种游戏称为NIM博弈。把游戏过程中面临的状态称为局面。整局游戏第一个行动的称为先手,第二个行动的称为后手。若在某一局面下无论采取何种行动,都会输掉游戏,则称该局面必败。
    所谓采取最优策略是指,若在某一局面下存在某种行动,使得行动后对面面临必败局面,则优先采取该行动。同时,这样的局面被称为必胜。我们讨论的博弈问题一般都只考虑理想情况,即两人均无失误,都采取最优策略行动时游戏的结果。
    NIM博弈不存在平局,只有先手必胜和先手必败两种情况。
    
    定理: NIM博弈先手必胜,当且仅当 A1 ^ A2 ^ ... ^ An != 0
 

证明过程点这!!!

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)。

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

有向图游戏的和
    设G1, G2, ..., Gm 是m个有向图游戏。定义有向图游戏G,它的行动规则是任选某个有向图游戏Gi,并在Gi上行动一步。G被称为有向图游戏G1, G2, ..., Gm的和。
    有向图游戏的和的SG函数值等于它包含的各个子游戏SG函数值的异或和,即:
        SG(G) = SG(G1) ^ SG(G2) ^ ... ^ SG(Gm)

定理
    有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0。
    有向图游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0。

/*
先手必胜状态:先手操作完,可以走到某一个必败状态
先手必败状态:先手操作完,走不到任何一个必败状态
先手必胜状态:a1 ^ a2 ^ a3 ^ ... ^an ≠ 0
先手必败状态:a1 ^ a2 ^ a3 ^ ... ^an = 0
*/
#include <iostream>
using namespace std;
int main(){
    int n;
    scanf("%d", &n);
    int res = 0;
    for(int i = 0; i < n; i++) {
        int x;
        scanf("%d", &x);
        res ^= x;
    }
    if(res) puts("Yes");
    else puts("No");
    return 0;
}

/*
先手必胜状态:a1 ^ a3 ^ ... ^an ≠ 0(n为奇数)
先手必败状态:a1 ^ a3 ^ ... ^an =  0(n为奇数) 
*/
#include <iostream>
using namespace std;
int main(){
    int n;
    scanf("%d", &n);
    int res = 0;
    for(int i = 1; i <=n; i++) { //i从1开始
        int x;
        scanf("%d", &x);
        if(i%2)res ^= x;
    }
    if(res) puts("Yes");
    else puts("No");
    return 0;
}

看过程就点我!!!

#include<iostream>
#include<unordered_set>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110,M=10010;
int s[N],f[M];//s存储的是可供选择的集合,f存储的是所有可能 出现过的情况的sg值
            //当s集合已经固定时 每个数的sg值就已经确定
int m,n;

int sg(int x){
	if(f[x]!=-1) return f[x]; //s集合已经固定 每个数的sg值已经确定 如果已经被算过 直接返回
	unordered_set<int> S;   //局部变量
	for(int i=0;i<m;i++){
		int sum=s[i];
		if(x>=sum) S.insert(sg(x-sum)); //先延伸到终点 从后往前计算所有数的sg值
		                       //当x比较大时 最多可以延伸m个节点 不同的节点可能会返回相同的值
	}
	for(int i=0;;i++)
		if(!S.count(i))	//从插入的节点中进行判断 找到S中没有的最小非负整数
		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));
                //将f数组初始值全部为-1 方便在sg中查看是否被标记过(即已经计算过 可以直接返回)
	int res=0;
	for(int i=0;i<n;i++){
		int x;
		cin>>x;
		res^=sg(x);//将每个数的sg值进行异或
	}
	if(res) puts("Yes");
	else puts("No");
	return 0;
} 

SG函数的递归过程 

 

#include<iostream>
#include<algorithm>
#include<unordered_set>
#include<cstring>
using namespace std;
const int N=110;
int f[N];
int n;

int sg(int x){
	if(f[x]!=-1) return f[x];
	unordered_set<int> S;
	for(int i=0;i<x;i++)
		for(int j=0;j<=i;j++)//防止重复用<=i  例如1 3和3 1
		S.insert(sg(i)^sg(j));
	for(int i=0;;i++)
		if(!S.count(i))
		return f[x]=i;
}
int main(){
	scanf("%d",&n);
	int res=0;
	memset(f,-1,sizeof(f));
	for(int i=0;i<n;i++){
		int x;
		cin>>x;
		res^=sg(x);
	}
	if(res) puts("Yes");
	else puts("No");
	return 0; 
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值