博弈论学习笔记

文章探讨了博弈论中的几个关键概念,包括先手必胜和必败的条件,公平组合游戏的定义,以及如何将这些游戏转化为有向图游戏。此外,介绍了运算和函数在解决此类问题中的应用,特别是记忆化搜索在计算游戏状态函数值时的作用。最后,给出了若干个具体的Nim游戏变体的解题示例,展示了如何通过异或操作来确定游戏的胜负情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

他人超详细的解析

1. $NIM$ 博弈


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


定理

$NIM$ 博弈先手必胜,当且仅当  $A_1\space xor \space A_2\space xor\cdots xor\space A_n \not= 0$

$NIM$ 博弈先手必败,当且仅当  $A_1\space xor \space A_2\space xor\cdots xor\space A_n = 0$


2. 公平组合游戏 $ICG$


若一个游戏满足:

  1. 有两名玩家交替行动。
  2. 在游戏过程的任意时刻,可以执行的合法行动与轮到哪名玩家无关。
  3. 不能行动的玩家判负。  

则称该游戏为一个公平组合游戏。
$NIM$ 博弈属于公平组合游戏,而常见的棋类游戏,如围棋,就不是公平组合游戏。因为围棋交战双方分别只能落黑子和白子,胜负判定也比较复杂,不满足条件 $2$ 和条件 $3$ 。


3. 有向图游戏


给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。


任何一个公平组合游戏都可以转化为有向图游戏。具体方法:把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。


4. $Mex$ 运算


$S$ 表示一个非负整数集合。定义 $mex(S)$ 为求出 不属于 集合 $S$最小非负整数的运算,即:
mex(S)=\min_{x\in N,x\notin S}\{x\}

5. $SG$ 函数


在有向图游戏中,对于每个节点 $x$,设从 $x$ 出发共有 $k$ 条有向边,分别到达节点 $y_1,y_2,\cdots,y_k$,定义 $SG(x)$ 为  $x$ 的后继节点 $y_1,y_2,\cdots,y_k$$SG$ 函数值构成的集合 再执行 $mex$ 运算的结果,即:
SG(x)=mex(\{SG(y_1),SG(y_2),\cdots,SG(y_k)\})


特别地,整个有向图游戏 $G$$SG$ 函数值被定义为有向图游戏起点 $s$$SG$ 函数值,即 $SG(G)=SG(s)$ 。

求 SG 函数使用到 记忆化搜索

int sg(int x){
    if(f[x]!=-1) return f[x];
    unordered_set<int> S;
    for(int i=1;i<=m;i++){
        if(x>=s[i]) S.insert(sg(x-s[i]));
    }
    for(int i=0;;i++)
        if(!S.count(i))
            return f[x]=i;
}


5. 有向图游戏的和


$G_1,G_2,\cdots,G_m$$m$ 个有向图游戏。定义为有向图游戏 $G$,它的行动规则是任选某个有向图游戏 $G_i$,并在 $G_i$ 上行动一步。 $G$ 被称为有向图游戏 $G_1,G_2,\cdots,G_m$ 的和


有向图游戏的和的 $SG$ 函数值等于它包含的每个子游戏 $SG$ 函数值的异或和,即:
SG(G)=SG(G_1)\space xor\space SG(G_2)\space xor\space\cdots xor\space SG(G_m)


定理

  1. 有向图游戏的某个局面必胜,当且仅当该局面对应节点的 $SG$ 函数值大于 $0$

  2. 有向图游戏的某个局面必败,当且仅当该局面对应节点的 $SG$ 函数值等于 $0$

例题:

1. AcWing 891. Nim游戏

NIM 游戏模板题

#include<bits/stdc++.h>
using namespace std;
int n;
int main(){
	scanf("%d", &n);
	int ans=0;
	for(int i=1;i<=n;i++){
		int x;
		scanf("%d",&x);
		ans^=x;
	}
	if(ans) printf("Yes\n");
	else printf("No\n");
	return 0;
}
2. AcWing 893. 集合-Nim游戏

有向图游戏模板题

运用到 SG 函数,此题 n 堆石子,对于每一堆石子求其 SG 的值,然后将 SG(x_1)\wedge SG(x_2)\wedge \cdots \wedge SG(x_n)

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int n,m;
int s[N],f[N];
int sg(int x){
	if(f[x]!=-1) return f[x];
	unordered_set<int> S;
	for(int i=1;i<=m;i++){
		if(x>=s[i]) S.insert(sg(x-s[i]));
	}
	for(int i=0;;i++)
		if(!S.count(i))
			return f[x]=i;
}
int main(){
	scanf("%d",&m);
	for(int i=1;i<=m;i++) scanf("%d",&s[i]);
	scanf("%d",&n);
	memset(f,-1,sizeof(f));
	int ans=0;
	for(int i=1;i<=n;i++){
		int x;
		scanf("%d",&x);
		ans^=sg(x);
	}
	if(ans) printf("Yes\n");
	else printf("No\n");
	return 0;
}
3. AcWing 892. 台阶-Nim游戏

对于偶数台阶上的石子,可以看作如果第一个人将偶数台阶上的石子移动 x 个到奇数台阶,那么第二个人可以相反,将奇数台阶上的石子移动 x 个石子到偶数台阶上。当到最后的一号台阶时,可以看作第一个人移动 x 个石子到一号台阶,第二个人可以将 x 个石子移到地面上,所以偶数台阶对答案无影响。

对于奇数台阶来说,让他们全部异或一下,当 ans\neq 0 时,先手必胜,反之必败。

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

int main(){
	int n;
	scanf("%d",&n);
	int ans=0;
	for(int i=1;i<=n;i++){
		int x;
		scanf("%d",&x);
		if(i%2) ans^=x;
	}
	if(ans) printf("Yes\n");
	else printf("No\n");
	return 0;
}
4. AcWing 894. 拆分-Nim游戏

题目大意:每一堆可以变成小于原来那堆的任意大小两堆

SG 函数理论,多个独立局面的 SG 值,等于这些局面 SG 值的异或和。因此 SG(y)=SG(i)\wedge SG(j),对于一堆 x 个的石子,其 SG(x)=mex(SG(y_1),SG(y_2),\cdots ,SG(y_t))

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int f[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++)
			S.insert(sg(i)^sg(j));
	// mex 操作
	for(int i=0;;i++)
		if(!S.count(i)) 
			return f[x]=i;
}
int main(){
	int n,ans=0;
	memset(f,-1,sizeof(f));
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		int x;
		scanf("%d",&x);
		ans^=sg(x);
	}
	if(ans) printf("Yes\n");
	else printf("No\n");
	return 0;
}
5. AcWing 1319. 移棋子游戏

有向图游戏模板题

求出每个点的 SG 值,然后将有棋子的点的 SG 值 XOR 在一起就是答案

#include<bits/stdc++.h>
using namespace std;
const int N=2010;
struct node{
	int to,nex;
}e[N*3];
int cnt,head[N];
int n,m,k,f[N];
void add(int u,int v){
	e[++cnt].nex=head[u];
	e[cnt].to=v;
	head[u]=cnt;
}
int sg(int x){
	if(f[x]!=-1) return f[x];
	unordered_set<int> S;
	for(int i=head[x];i;i=e[i].nex){
		int y=e[i].to;
		S.insert(sg(y));
	}
	for(int i=0;;i++)
		if(!S.count(i))
			return f[x]=i;
}
int main(){
	memset(f,-1,sizeof(f));
	scanf("%d%d%d",&n,&m,&k);
	while(m--){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
	}
	int ans=0;
	for(int i=1;i<=k;i++){
		int x;
		scanf("%d",&x);
		ans^=sg(x);
	}
	if(ans) printf("win\n");
	else printf("lose\n");
	return 0;
}
6. AcWing 1321. 取石子

需要推结论,不太会

他人优秀题解

#include<bits/stdc++.h>
using namespace std;
const int N=55,M=50050;
int f[N][M];
int dp(int a,int b){
	int &v=f[a][b];
	if(v!=-1) return v;
	if(!a) return b%2;
	if(b==1) return dp(a+1,0);
	
	if(a&&!dp(a-1,b)) return v=1;
	if(b&&!dp(a,b-1)) return v=1;
	if(a>=2&&!dp(a-2,b+(b?3:2))) return v=1;
	if(a&&b&&!dp(a-1,b+1)) return v=1;
	
	return 0;
}
int main(){
	int T;
	scanf("%d",&T);
	memset(f,-1,sizeof(f));
	while(T--){
		int n,a=0,b=0;
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			int x;
			scanf("%d",&x);
			if(x==1) a++;
			else b+=b?x+1:x;
		}
		if(dp(a,b)) printf("YES\n");
		else printf("NO\n");
	}	
	return 0;
}
7. 1322. 取石子游戏

很难,用到区间 dp 和推结论,不太会

他人优秀题解

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int l[N][N],r[N][N],n,a[N];
int T;
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int len=1;len<=n;len++){
            for(int i=1;i+len-1<=n;i++){
                int j=i+len-1;
                if(len==1) l[i][j]=r[i][j]=a[i];
                else{
                    int L=l[i][j-1],R=r[i][j-1],X=a[j];
                    if(R==X) l[i][j]=0;
                    else if((X<L&&X<R)||(X>L&&X>R)) l[i][j]=X;
                    else if(L>R) l[i][j]=X-1; 
                    else l[i][j]=X+1;

                    L=l[i+1][j]; R=r[i+1][j]; X=a[i];
                    if(L==X) r[i][j]=0;
                    else if((X<L&&X<R)||(X>L&&X>R)) r[i][j]=X;
                    else if(R>L) r[i][j]=X-1;
                    else r[i][j]=X+1;
                }
            }
        }
        if(n==1) printf("1\n");
        else printf("%d\n",l[2][n]!=a[1]);
    }
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值