公平组合游戏

公平组合游戏

Nim 游戏#

参考

概述与解法#

Nim 游戏是 ICG(Impartial Combinatorial Games)

满足下列条件的游戏才算 ICG

  1. 两个人
  2. 两个人交替对游戏进行移动,每次一步,选手可以在(一般而言)有限的合法移动集合中任选一种进行移动
  3. 对于游戏的任何一种可能的局面,合法的移动集合只取决于这个局面本身,不取决于轮到哪名选手操作、以前的任何操作、骰子的点数或者其它什么因素
  4. 如果轮到某名选手移动,且这个局面的合法的移动集合为空(无法移动),则这名选手 lose 。(根据这个定义,很多日常的游戏并非 ICG)

满足上述条件的游戏才可以使用之后叙述的 Nim 游戏的解法来解决

Nim 游戏及解法#

通常的Nim 游戏的定义是这样的:有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判lose(因为他此刻没有任何合法的移动)。

定义 PpositionNposition ,其中P代表 PreviousN 代表 Next

直观的说,上一次 move 的人有必胜策略的局面是 Pposition,也就是“后手可保证必胜”或者“先手必败”,现在轮到 move 的人有必胜策略的局面是 Nposition,也就是“先手可保证必胜”

更严谨的定义是:1.无法进行任何移动的局面(也就是 terminal position)是Pposition;2.可以移动到Pposition 的局面是 Nposition;3.所有移动都导致Nposition的局面是Pposition

Nim 游戏的结论:若是对于一个游戏局面 a1n,它是 Ppostion 当且仅当 a1a2an=0

这个证明也显然,若是满足 a1a2an=0 ,则对于 a1n 其所有二进制位上的 1 有偶数个

也就是如果之后先手取一个那么后手就取对应的一个,最后总是先手的取不到

这样先手就输了

有向图游戏与 SG 函数#

参考

概述#

大部分公平组合游戏都可以转换为有向图游戏,即:

  • 在一个有向无环图中,只有一个起点,上面有一个棋子,两个玩家轮流沿着有向边推动棋子,不能走的玩家判 lose

我们定义 mex(S) 函数:

mex(S)=min{x}(xN,xS)

如:mex{1,2,3}=0,mex{0,1,3}=2

对于状态 x 和它所有的 k 个后继状态 y1k,定义其 SG 函数:

SG(x)=mex{SG(y1),SG(y2)SG(yk)}

而对于有 n 个有向图组成的组合游戏,设其起点分别为 s1n ,则有定理:

当且仅当 SG(s1)SG(s2)SG(sn)0 时先手必胜,同时,这是这一个组合游戏的游戏状态的 SG 值。

这一定理被称作 SpragueGrundy(SG) 定理

可以把 SG 类比 Nim 来理解

Nim 游戏可以转化为一个有向图游戏用 SG 定理求解

习题#

P2197 【模板】nim 游戏#

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

#define re register

inline int read(){
	re int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){ch=getchar();}
	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}

signed main(){
	re int T=read();
	while(T--){
		re int n=read(),ans=0;
		for(re int i=1;i<=n;++i) ans^=read();
		puts(ans?"Yes":"No");
	}
}

P1247 取火柴游戏#

思路

这题多了一个输出方案

按照 Nim 游戏的解法,我们先求出 chk=a1a2an

chk=0,即 Pposition,则先手必输

否则,我们考虑让电脑在我们取完第一次之后变为 Ppositon

也就是让某一个 ai 变为 chkai,这样就可以达到目的

判断一下 chkai 是否大于等于 ai 就行 (不能不取,也不能拿负数个)

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

const int N=5e5+5;

inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}

int n;
int a[N];

signed main(){
	n=read();
	int chk=0;
	for(int i=1;i<=n;++i){
		a[i]=read();
		chk^=a[i];
	}
	if(!chk){
		puts("lose");
		return 0;
	}
	for(int i=1;i<=n;++i){
		if((chk^a[i])>=a[i]) continue;
		printf("%d %d\n",a[i]-(chk^a[i]),i);
		a[i]=chk^a[i];
		for(int j=1;j<=n;++j) printf("%d ",a[j]);
		break;
	}
}

作者:Into_qwq

出处:https://www.cnblogs.com/into-qwq/p/16473469.html

版权:本作品采用「qwq」许可协议进行许可。

posted @   Into_qwq  阅读(145)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示