算法 {博弈论,组合游戏,SG游戏}

算法 {博弈论,组合游戏,SG游戏}
@LOC_COUNTER: 10

博弈论

定义

博弈论非常庞大, 根据算法来分类:
1: SG游戏; (通过对SG值进行异或操作)
2: DAG游戏; (直接对整个DAG图进行DFS遍历, 每个节点设置一个bool的胜负态);
3: 思维技巧型; (没有统一算法…)

相关定义

#局面有向图#;

以初始局面为起点, 根据游戏规则的定义, 枚举所有可能的操作 得到新局面, 从而构成一个弱连通的有向图;
. 比如a -> b意味着: 当局面是a时, 操作之后 会变成b;

性质

有一类游戏 跟组合游戏很相像, 但只有1点不同: 其局面有向图 不是DAG 而是存在环的 (如果没有环 他就是组合游戏); 但是, 虽然有环 但可以证明 在双方采取最优策略下 一定不会进行死循环(即平局) 而是一定以一胜一负结束;
. 此时, 你需要关注 所有双方都采取最优策略下的路径, 这种路径有个性质: 对于任意一点, 如果他是必胜态 则他的{前驱,后驱节点}一定是必败态; (即不断胜负交替)
. 即, 其局面有向图 虽然不是DAG, 但是每个节点 也满足: 要么是必胜态 要么是必败态;
LINK: ;

非组合游戏

定义

不是通过{SG定理/ 遍历整个局面DAG}求解的;

例题

找到必败态的等价条件: LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=131363391;

组合游戏

定义

组合游戏 Combinatorial game theory, 需要满足:
1: #最优性# LR两人 轮流操作(且确立谁先手), 且都采用最优策略 (只要能让对手输 我就赢了; 否则不管我怎么选择对手都赢 则我就输了);
2: #有限性#: 不管怎样操作, 游戏一定会在有限次操作后 变成终止态一胜一败结束 (不会是平局);
3: #确定性#: 该游戏的局面有向图 (即所有局面节点(一个节点由{棋盘状态 + 谁操作}组成)) 是一个DAG, 该DAG只有1个起点(初始局面) 所有的叶子节点都是终止态局面(意味着游戏结束 胜负已定);
. #推论#: 所有可能的局面 (即{棋盘状态 + 谁操作}) 要么是必胜态 要么是必败态 (即游戏无需进行 便知胜负);

@DELI;

为什么称之为组合Combinatorial?
该游戏DAG上任意一个节点 都可视为一个单独的游戏; 比如对于一个游戏 他的初始局面是X 其DAG图上有Y,Z后驱节点, 那么对于Y 可以把他当成是一个独立的游戏 (即一个游戏 他的初始局面是Y) 他跟X是完全无关的, 也就是 Y, Z是两个独立的游戏 (比如说 他俩的结果 是一个必胜 一个是必败), 那么现在要进行一个新游戏X 其实他的结果已经可以确定了 因为他由Y,Z组合而成X = {Y, Z}, 后驱节点存在必败态 因此X为必胜态;
. 这是组合一词的含义, 即每个节点/局面 都可以视为一个独立的游戏, 因此 一个初始状态为X的游戏 其实由所有以后驱节点为初始状态的独立游戏 组合而成;

相关定义

#组合游戏DAG#;

满足:
1: 只有1个起点 (即该起点可以到达所有点);
2: 每个点都有一个胜负态的属性 (要么是必胜态, 要么是必败态), 且要满足: 对于非叶子节点C, 如果他的某个儿子是必败态 则C是必胜态; 否则C的所有儿子都是必胜态 则C为必败态 (对于叶子节点 其胜负态随意, 由游戏所规定 因为叶子节点对应为游戏的终止态 即当游戏到了叶子节点 游戏就结束了);

@DELI;

组合游戏DAG 依据叶子节点的胜负态, 可分为3类:
1: #标准型组合游戏DAG# Normal play convention;
. 所有的叶子节点, 都是必败态; (等价于: 游戏对终止态局面的定义为: 当前玩家失败);
2: #反常型组合游戏DAG# Misere;
. 所有的叶子节点, 都是必胜态; (等价于: 游戏对终止态局面的定义为: 当前玩家胜利);
3: #其他型组合游戏DAG#;
. 如果所有叶子节点里 既有必胜态 又有必败态;

@DELI;

#终止局面# Terminal Position;

游戏规定 当局面满足怎么样的条件时 游戏结束 (且胜负已定), 则把满足这样的条件的局面 称为终止局面 (其对应于DAG里的叶子节点);
. 终止局面 是完全根据游戏规则来定义的, 他可以是必胜态 也可以是必败态;

@DELI;

组合游戏 依据操作的定义 是否因人而异, 可分为2类: (中立的imparital)(非中立的 partisan);

性质

Li表示一个组合游戏DAG的i级叶子节点 (L1为一级…), 则一定有:
1: 如果L1都是必胜态, 则L1,L3,L5...都是必胜态 L2,L4,L6...都是必败态;
2: 如果L1都是必败态, 则L1,L3,L5...都是必败态 L2,L4,L6...都是必胜态;
因为: Li叶子节点的所有子节点 一定都是 L(i-1)级叶子节点;

@DELI;

所谓组合游戏, 即任意局面 都是一个单独的游戏; 这个概念很重要 尤其是在SG游戏;

@DELI;

#所有标准型组合游戏DAG, 都等价于 DAG上移动棋子游戏#;

详见: LINK: @LOC_2;

@DELI;

对于组合游戏DAG, 不关心他是{标准型/反常型/…} (即不关系他叶子节点的胜负态), 对于一个非叶子节点X, 如果其所有后驱节点都是必胜态    ⟺    \iff X为必败态, 否则存在后驱节点为必败态    ⟺    \iff X为必胜态;

即不管是{标准/反常/…}组合游戏DAG, 他们的非叶子节点 都有这个性质! 其实 这已经不是个性质了, 非叶子节点的胜负态的定义 就是这样定义的, 具体算法为:
组合游戏DAG的拓扑序, 一定形如 [ . . . 四级 , 三级 , 二级 , 一级叶子节点 ] [... 四级, 三级, 二级, 一级叶子节点] [...四级,三级,二级,一级叶子节点], 以逆序方式遍历该序列:
对于一级叶子节点: 其胜负态取决于 游戏规则里对终止态怎么定义的, 因此要特判;
对于 X > 1 X>1 X>1级叶子, 其所有后驱节点(一定是<=(X-1)级叶子节点)的胜负态都已经确定, 根据上面的规则 就可以确定当前节点的胜负态;

@DELI;

MARK: @LOC_8;
#只要组合游戏DAG的点数 > 1 > 1 >1, 那么他一定 (既有必胜态的节点, 又有必败态的节点)#

只需分析(标准型,反常型)DAG, 因为否则 叶子节点已经满足条件了;
#引理#: (1: 点数 > 1 >1 >1的DAG 一定存在二级叶子节点)(2: 二级叶子节点的所有后驱节点 均是一级叶子节点);
根据这两条引理, 对于标准型DAG: 二级叶子节点一定是必胜态, 对于反常型DAG: 二级叶子节点一定是必败态;

@DELI;

将组合游戏里的(局面 + 谁操作)作为图论的一个节点, 向 操作后的(局面 + 另一人)的节点 连接一条有向边(边表示操作), 则构成一个组合游戏DAG;
. 即: 任何组合游戏    ⟺    \iff 组合游戏DAG;

@DELI;

组合游戏, 不是公平游戏 (这里指的是fair, 而不是博弈论里的impartial);

因为 {初始局面 + 谁先手}这个问题确定后, 胜败就决定了! 与人的智商无关, 当然不是公平的;

@DELI;

象棋不是组合游戏, 因为(L人 前进一步, R人也前进一步, L人再退回来, R人也退回来, 如此往复循环), 所以他不是个 D A G DAG DAG图;

五子棋是组合游戏, 因此 对于任意一个 n ∗ m n*m nm的空局面 然后确定谁先手, 此时胜败已成定局 (因为他是个DAG图, 我们 通过暴力DFS就可以判定出来);

@DELI;

MARK: @LOC_0;
组合游戏的取胜规则 有2个版本:
1: normal play正常版本; 当前局面无法进行操作    ⟺    \iff 当前玩家失败;
2: misere反常版本; 当前局面无论怎么操作后 新局面让对手无法操作    ⟺    \iff 当前玩家失败;

但也不用去记忆这两种版本, 因为不管是什么取胜规则 他判定的 就是终止态, 这点是相同的, 只要到了终止态 游戏就结束了;
换句话说, 你只需关注 DAG图里的叶子节点 怎么定义, 这非常非常重要! 判定什么局面是叶子节点 也就等价于判定 当出现当前局面时 游戏就结束了 并且, 谁面临当前局面 谁就是必败的;

举例:
1: 对于正常版本, (终止态/叶子节点)    ⟺    \iff (该局面无法进行操作);
2: 对于反常版本, (终止态/叶子节点)    ⟺    \iff (该局面虽然可以操作, 但没有继续进行操作的必要, 因为不管怎么操作 后驱局面都是无法操作的);
可以看到, 虽然不同的取胜规则 导致对终止态的定义 是不同的, 但如果从图论的角度 你要做的 就是保证: 叶子节点 就是终止态;

错误

@DELI;

#终止局面 就是无法再进行操作的局面#;

非常非常容易犯这个错误, 其实 怎样的局面是终止态 这要看你的取胜规则是怎么定义的, 终止态是必败态这是肯定的 他与是否可以继续操作无关, 可能当前局面还能操作 但是当前局面是终止态 (即谁面临这个局面 谁是必败的; 比如反常版本就是这样的);

算法

因为他是个DAG图, 遍历其所有的节点bool DFS( point), 因为所有叶子节点 都符合结束游戏的条件 此时可以判断其到底是 胜利还是失败;
对于非叶子节点: (1: 如果他的儿子节点 全都是必胜态, 则当前节点为必败态)(2: 否则 当前节点为必胜态);

bool DFS( point){ // `point`由{局面, 谁操作}组成;
	if( point是叶子节点){
		return true/false;
	}
	
	for( nex : `point的儿子节点`){ // 即所有可能的操作
		if( false == DFS( nex)){ // 儿子是必败态
			return true; // 当前是必胜态
		}
	}
	return false;
}

就是个简单的图的遍历, 不需要记忆化 因为是DAG图; 也不要跟SG函数有牵连 那是公平组合游戏的事情 这里很简单 就是个DFS遍历;
既然提到SG函数 多说一句, 你使用SG函数作为返回值也可以(当然效率大大降低); 即叶子节点返回0 然后非叶子节点对其所有儿子的返回值进行MEX操作, 其实最终达到的效果 和这里直接使用bool返回值, 是一样的;

但几乎不会使用这样的做法 (否则也太简单了…), 因为 这个DAG图 可能非常非常大, 注意这个DAG是要包含所有局面的, 即所有可能的操作 导致所有的局面 都要体现在这个DAG图里, 因此 通常去遍历这个DAG图 几乎不可行;

组合游戏的算法:
1: 上面讲的这种暴力DFS方式;
2: 下面会讲到的 SG游戏;

组合游戏/中立的

定义

对于组合游戏G, 如果对于任意局面: 让玩家L操作后的所有新局面是S1, 让玩家R操作后的所有新局面是S2, 那么一定有 S 1 = S 2 S1 = S2 S1=S2; 此时组合游戏G 称为中立的Impartial;
. 即, 在组合游戏里 其DAG图的节点 由{局面, L/R}组合, 而在中立组合游戏里 DAG图的节点 可以只由局面组合 无需关注谁操作;

性质

有人把Impartial翻译为公平的, 我认为不好,

@DELI;

在组合游戏里, 对于同一局面P, 可能(P, L)节点是必胜的 而(P, R)节点是必败的; 但是在公平组合游戏 (P, L)(P, R)的 胜败态 一定是相同的, 因此 我们无需关注这个局面是谁在操作, 只需关注最优策略下 操作这个局面, 是否可以必胜这个问题;
. 也就是, 胜负态 是绑定在局面上的 与玩家无关, 如果一个局面是必胜的 那么谁来操作都是必胜的;

@DELI;

SG游戏(独立集游戏)

定义

SG: Sprague, Grundy 两个人名;

#游戏描述#: 给定 > 1 >1 >1只有1个起点的 有限的 DAG {S1, S2, ...}, 他们的起点上 有1个棋子 (对应为下面的标题SG游戏/经典模型/DAG上移动棋子游戏, 也就是: 标准型中立组合游戏DAG);
#游戏操作#: 选择一个棋子不在叶子节点的DAG, 将该图上的棋子 移动到 任意后驱节点上;
#游戏终止#: 如果所有DAG的棋子 都位于叶子节点, 则当前玩家失败;

即 当前SG游戏的局面X, 等价于 一个独立集{S1, S2, ...} (每个Si是一个标准型中立组合游戏 即上面讲的DAG上移动棋子游戏 他俩等价);
. 即X 可以表示成(P1, P2, ...) (Pi为标准型中立组合游戏Si的局面);

@DELI;

有点抽象, 但他确实非常重要 SG游戏是算法博弈论的主流, 因此下面举个例子;
在这里插入图片描述
即有2个DAGG, G', 初始时 A, A'各有1个棋子, 我们以(A, A')表示该SG游戏的局面, 那么该SG游戏DAG 如图所示 (由于他是标准型组合游戏DAG, 因此他可以定义SG函数, 橙色数字表示其SG值);
初始局面(A, A')是必胜的, 但如果初始局面是(C,A') 他是必败的;

性质

SG游戏 比如某个局面是(P1, P2, ...) Pi是一个DAGGi的初始节点;
如果通过朴素做法 即遍历该SG游戏的整个局面DAG, 这是不可行的 他非常非常大! 他的节点个数 比|G1| * |G2| * ...还大的多!
SG定理表明, 你无非就是想求(P1, P2, ...)这1个局面的SG值, 他就等于 PiGi的SG值 的异或和; 即此时 时间复杂度转换为了|G1| + |G2| + ...;

@DELI;

不要讲: 某个游戏 是SG游戏, 这是错误的;
因为组合游戏, 每个局面(即节点) 都是一个独立的游戏, 换句话说 同一个组合游戏G, 他的不同局面 是不同的(虽然游戏规则等都相同);
有点含糊, 这里要讲的问题是: 同一个游戏G, 他的有些局面X 可以表示成SG游戏 即计算X的SG值 无需通过求其后驱节点的SG值然后取MEX这种常规操作 而是可以将X表述成(P1, P2, ...) (每个Pi是一个子DAGSi的局面) 那么求所有PiSi里的SG值的异或值 就等于X的SG值 (这是SG游戏即SG定理的核心), 但是G的有些局面 无法表示成SG游戏 (即转换成子游戏DAG); 因此, 不要说某个游戏G是SG游戏, 应该讲 某个游戏G的某个局面X 他是SG游戏;
详见: LINK: @TODO;

@DELI;

SG游戏是 组合游戏;
1: #最优性# 成立;
2: #有限性# 由于所有DAG是有限的, 成立;
3: #确定性# 由于棋子只能后驱移动(不能往前移动), 因此其局面有向图 一定是个DAG;

SG游戏是 中立的; 因为从游戏操作可看出 两个人的操作是相同的;

SG游戏是 标准型; 因为终止态局面定义为 当前玩家失败;

算法

#SG定理#
SG游戏的初始局面为X, 其对应为: > 1 >1 >1个标准型中立组合DAG{G1, G2, ...} 且他们的起点依次为{S1, S2, ...}, 则:

(由于{SG游戏, Gi} 都是标准型中立组合游戏) 他们都可以定义SG值, 则SG定理表明: 初始局面X的SG值    ⟺    \iff 所有Si的SG值的异或和;

@DELI;

在#定义#章节里有个图, 根据其DAG图 可知起点的SG值为3, 而A在其子DAG里的SG值为2, A'在其子DAG里的SG值为1, 因此1^2 = 3;
即, 这个SG游戏 他的DAG图非常大(通过暴力遍历整个DAG图 构造其每个节点的胜负态, 是不可行的 会超时), 而(A, A')他的SG值 无需通过遍历整个DAG图获得, 他就等于 A的SG值 ^ A'的SG值;

证明: 令SG游戏为G, 初始局面 可等价为 独立集{S1, S2}标准中立DAG (对于 > 2 >2 >2的情况 可通过数学归纳法证明);
1: 对于G的DAG里的叶子节点X, 即(L1, L2) (他们都是Si里面的叶子节点), X的SG值 为0, 而Li的SG值也是0, 此时成立(0^0) = 0;
2: 对于G的DAG里的非叶子节点X = (P1, P2) (Pi 不会全都是叶子节点), 以*二级->三级->四级叶子节点->…*的顺序遍历 (也就是此时他的所有后驱节点(x,y)的SG值 满足定理 即满足SG( (x,y)) = SG(x) ^ SG(y));
a = SG(P1), b = SG(P2), 令其子节点为(PP1, PP2)c = SG(PP1), d = SG(PP2), 则一定有: c = [0, a), d = b; c = a, d = [0, b); 可能有c > a, d = b 可能有c = a, d > b;
. 举个例子说明下, 比如当前SG值为(a=2, b=3), 那么他的子节点(SG值) 一定有(0,3) (1,3) (2,0) (2,1) (2,2) 可能有(>2, 3) 可能有(2, >3);
. 注意 根据前提, 此时已经有: 其所有后驱节点的SG值 就等于子游戏的异或值, 因此SG( (PP1,PP2)) = c ^ d;
. 对于后两种可能情况 (即变大的情况 c>a, d=b; c=a,d>b), 他的SG值 一定不会等于 a^b (根据异或性质);
.a^b = T, 根据异或性质, 对于变小的情况 他们一定会覆盖掉[0, T); LINK: (https://editor.csdn.net/md/?articleId=126951968)-(@LOC_0); .因此综上, 当前(a,b)的SG值 等于a^b;

@DELI;

对于一个游戏G 其初始局面为X, 你无非想要的 就是求X的胜负态! 此时有2种情况:
1: X是SG游戏, X = (P1, P2, ...) Pi为一个标准型中立DAG的初始局面, 即对X操作 等价于 对(P1, P2, ...)独立集的操作, 那么X的SG值 等于Pi的SG值的异或值;
2: X不是SG游戏, 那么令X的所有后驱节点为S, 此时的目的转换为: 求S的SG值 (然后通过MEX就可以求X的SG值), 因此 假如其所有后驱节点S 都是SG游戏, 这样后驱节点S的SG值 就不需要(通过递归整个局面DAG来求) 而是通过SG定理来求;

SG游戏/经典例题

Nim游戏

定义

给定一个数字 X ≥ 0 X \geq 0 X0, 操作: 选择0 < k <= X 执行X -= k; 终止态: X = 0时 当前玩家失败;

他是标准型中立组合游戏;

其局面DAG为:

X=3
3--0(0)
 --1(1)--0(0)
 --2(2)--0(0)
       --1(1)--0(0)
`a(b)表示: 当前局面是a, b表示其SG值`;

性质

可以发现: 一个局面X 他的SG值 就等于X; (因此 X > 0 X > 0 X>0必胜);

Nim游戏/SG游戏

MARK: @LOC_5;

定义

给定若干个数字 ( X 1 , X 2 , . . . ) , X i ≥ 0 (X1,X2,...),X_i \geq 0 (X1,X2,...),Xi0, 操作: 选择一个数Xi, 选择0 < k <= X 执行Xi -= k; 终止态: 所有Xi为0时 当前玩家失败;

他是标准型中立组合游戏;

其局面DAG为:

[1,2](3)--[0,2](2)--[0,1](1)--[0,0](0)
			      --[0,0](0)
        --[1,1](0)--[0,1](1)--[0,0](0)
                  --[1,0](1)--[0,0](0)
        --[1,0](1)--[0,0](0)
`[a](b)表示: a为局面, b为其SG值`;

他是SG游戏, 证明:
对于(X1, X2, ...), 令Si表示朴素Nim游戏(X = Xi), 可以发现 该游戏 等价于: 每次选择一个不在终止态的Si 然后将Si移动到后驱节点;
因此, 对于初始局面(X1, X2, ...) (虽然他的局面DAG非常非常大 通过遍历来构造胜负态不可行), 根据SG定理: 他在局面DAG里的SG值 就等于 XiSi的SG值(根据朴素Nim游戏, 他的值等于Xi)的异或和;

例题

LINK: https://editor.csdn.net/md/?articleId=131420502;

Nim游戏/拆分Nim

定义

MARK: @LOC_6;

给定集合S = {X>=0}, 操作: 从集合中选择1个数Xi, 选择0 <= a,b < X, 将Xi替换为a,b; 终止态: 如果集合所有元素都是0 则当前玩家失败;

S = {2}

{3}(4)--{00}(0)
	 --{01}(1)--{000}(0)
	 --{02}(2)--{000}(0)
	          --{001}(1)--{0000}(0)
	 		  --{011}(0)--{0001}(1)--{00000}(0)
	 --{11}(0)--{001}(1)--{0000}(0)
	 --{12}(3)--{002}(2)--{0000}(0)
	                    --{0001}(1)--{00000}(0)
	                    --{0011}(0)--{00001}(1)--{00000}(0)
	 		  --{001}(1)
	  		  --{011}(0)
	  		  --{111}(1)--{0011}(0)--{00001}(1)--{000000}(0)
	  --{22}(局面DAG太大了,省略...)
{a}(b)表示: 局面是a, 其SG值为b

注意, 你可能觉得 {a,b,c}的SG 就等于a^b^c, 这是错误的! (虽然图中有些局面确实满足, 但不可以以偏概全, 还是用通过严格的推理证明);
因为: {X}的SG 并不是X (比如{3}的SG值为4);

虽然他的初始局面{X}不是SG游戏, 但其儿子节点{a,b} 等价于 (拆分Nim(a), 拆分Nim(b))这个独立集 他是SG游戏!
因此, 对于{X} -> {a,b} 我们需要求: a这个独立的拆分Nim的SG值; 但是现在 画出a这个子DAG 仍然十分困难 因为他也形如a -> {a1,b1} -> ... 非常大, 因此继续递归的处理, 即a虽然不是SG游戏 但a的儿子 {a1,b1}是SG游戏;

变形

LINK: @LOC_7;

初始局面是{X1, X2, ...}, 他是SG游戏, 每个Xi就是朴素的拆分Nim;

做法是: Get_SG( X1) ^ Get_SG( X2) ^ ...;

算法

int Get_SG( int _a){ // `a >= 0`;
    static int __record[ 102];
    { static bool __is_first = true; if( __is_first){ __is_first = false; memset( __record, -1, sizeof( __record));}}
    auto & ANS = __record[ _a];
    if( ANS != -1){ return ANS;}
    //--

    { // 检查终止态;
        bool is_leaf = false;
        if( _a == 0){
            is_leaf = true;
        }
        if( is_leaf){ return ANS = 0;}
    }

    unordered_set< int> mex;
    for( int a = 0; a < _a; ++a){
        for( int b = a; b < _a; ++b){
            auto aa = Get_SG( a);
            auto bb = Get_SG( b);
            mex.insert( aa ^ bb);
        }
    }
    //> 对`mex`进行MEX操作;
    for( ANS = 0; ; ++ANS){
        if( mex.find( ANS) == mex.end()){
            break;
        }
    }
    ASSERT_( ANS > 0);
    return ANS;
}

例题

LINK: https://editor.csdn.net/md/?articleId=131488394;

DAG上移动棋子游戏

定义

给定一个DAG, X点上有一个棋子, 操作定义为: 将该棋子 移动到 任意后驱节点上, 终止局面为: 该棋子位于叶子节点时 必败;

令G为该DAG的子图: 由所有X可以到达的点和边组成(X无法到达的点和边 无需关注), 则G为标准型的组合游戏DAG;
即, 所有叶子节点为必败态, 然后递归定义 如果一个非叶子节点的所有后驱为必胜态 则其为必败态, 否则为必胜态;

算法: 使用组合游戏的遍历DAG算法 (即获取每个节点的胜负态), 从而得到初始局面的胜负态;

性质

MARK; @LOC_2;
#所有标准型组合游戏DAG, 都等价于 DAG上移动棋子游戏#;

对于标准型组合游戏DAG, 他的游戏规则 即从初始局面(即起点) 操作之后 变成了其某个后驱局面, 这个操作 就等价于: 你想象在该DAG的起点上 有一个棋子 然后每次操作把这个棋子移动到后驱节点上;
游戏终止为: 如果局面变成了任意终止态局面(即叶子节点的局面) 则谁面临谁失败; 这个也等价于: 当棋子移动到了叶子节点 谁面临谁失败;

因此, 这两个游戏 其实是完全等价的; 只不过 一个是局面在变化 另一个是在移动棋子;

DAG上移动棋子游戏/SG游戏

MARK: @LOC_4

定义

给定一个DAG, 上面有若干个棋子1,2,3,..., 每次操作: 选择任意一个不在叶子节点上的棋子 将它移动到后驱节点上; 终止局面: 所有棋子均在叶子节点上 则当前玩家失败;

该游戏为SG游戏, 证明:
Gi表示 该DAG上只有1个i号棋子 的DAG上移动棋子游戏 (即普通版本的 DAG上移动棋子游戏);
. 根据DAG上移动棋子游戏的性质, 其局面DAG 就等于 这个图DAG, 记i号棋子所在的节点位置为Pi (Pi不仅是DAG的节点, 也是其局面DAG的局面);
则可发现: 当前游戏的局面X 等价于 (P1, P2, ...) (每次选择1个不在叶子节点上的Pi 让他移动到后驱节点);

性质

因此, 如果你去根据X局面 去遍历整个他的局面DAG(注意 不是题目里给定那个DAG), 肯定是超时的! 这个图是非常非常大的 (P1', P2, ...), (P1, P2', ...) ...;
但是, 因为SG( (P1,P2,...)) = SG(P1) ^ SG(P2) ^ ..., 所以只通过|DAG|就可以解决;
. 即对这个DAG图构造SG值, 然后把所有棋子所在的节点的SG值 异或起来 就是答案;

例题

模板: LINK: https://editor.csdn.net/md/?articleId=131501592;

SG游戏/2种常见题型

题型1: 初始局面本身就是SG游戏

定义

游戏G的初始局面为(P1, P2, ...) 为SG游戏, 其中每个Pi就是一个独立的标准型中立DAG;

这种题比较简单, 直接求Pi在其DAG上的SG值即可;

例题

LINK: @LOC_5;

LINK: @LOC_4;

题型2: 初始局面不是SG游戏, 但其所有后驱节点是SG游戏

定义

游戏G的初始局面 可能不是SG游戏(例如LINK: @LOC_6) 也可能是SG游戏(形如{X1,X2...}; 但即便是SG游戏, 通过遍历Xi的DAG来求其SG值不可行, Xi不是SG游戏 但他的后驱节点是SG; 例如LINK: @LOC_7), 他是递归的定义的

此时的做法是: 他的后驱节点 一定形如{X1,X2,...} 他一定是SG游戏, 虽然直接求Xi在其DAG的SG值 不可行, 而且Xi不是SG游戏 但Xi的后驱节点 是SG游戏, 因此继续拆分 他是递归的进行的;

例题

LINK: @LOC_6;

LINK: @LOC_7;

将反常型DAG转换为标准型DAG: LINK: https://editor.csdn.net/md/?articleId=131472839;

SG游戏/将非标准型转换为标准型

定义

SG游戏的前提是: 他是标准型, 但如果他不是标准型 也可能可以把他转换为标准型;

因为对于一个点数 > 1 >1 >1的组合游戏DAG, 你删除必胜态的叶子节点, 并不会影响起点局面的胜负态 (我们的唯一目的, 就是求起点局面的胜负态 其实并不关心其后代节点)

a(V)--b(V)
    --c(X)
`V为必胜态, X为必败`;

你把叶子节点b删除掉 这个图 仍然是符合组合游戏DAG的要求, 但是如果你把叶子节点c删除掉 这就出错了 此时a(V)--b(V) 显然根据组合游戏DAG要求 (如果叶子节点全都是必胜    ⟺    \iff 当前是必败态) 因此a应该是必败态;
. 因为 任意一点的胜负态 其实看的是是否有 必败态的儿子节点, 只要你不删除必败态的节点, 删除必胜态叶子节点(其实 删除必胜态的非叶子及其子树也可以, 但好像不常用) 该DAG依然符合组合游戏DAG的要求 注意前提是不能删除根节点 这是自然的 否则根都没了你的答案没了;

根据LINK: @LOC_8, 对于一个点数 > 1 >1 >1非标准型中立组合游戏DAG, 递推执行: 将其所有必胜态的叶子节点删除掉, 最终一定会变成一个点数 > 0 >0 >0标准型中立组合游戏DAG;

算法

你的做法可能是: 令L为原来的叶子节点, 然后你去推理 满足怎样的条件的非叶子节点N 他的后驱节点都是L, 那么 这个N(一定是必败态) 就是 新DAG的叶子节点;
这个思路是正确的 但比较复杂 你需要进行逻辑推理, 有更简洁的做法;

原来你的DAG是:
DFS( cur){ `cur`为DAG的节点;
	if( cur为终止态){ // 必胜态
		return;
	}
	
	for( nex : cur){
	}
}

现在改为:
DFS( cur){ `cur`为DAG的节点;
	if( cur为终止态){ // 这里的if判断和上面版本一样;
		ASSERT; 报警!
	}
	
	int cont = 0;
	for( nex : cur){
		if( nex为终止态){ // 跟上面的if判断跟上面的都一样;
			continue;
		}
		++ cont;
	}
	`cont为0, 则cur为新DAG的叶子节点 即必败态`;
}

这个做法很巧妙, 你无需人为的去找 新的叶子节点符合怎样的条件 (因为可能很复杂), 现在 你就按照原来遍历DAG的方式 只不过 跳过原来的叶子节点, 这样只要cont=0 就是新DAG的叶子节点;

非标准型游戏, 转换为: SG游戏

MARK: @LOC_9;

给定若干个中立组合游戏DAGG1, G2,... 令其起点为P1,P2,..., 定义游戏:
初始局面为(P1, P2, ...), 操作为: 选择一个Pi将其移动到后驱节点, 终止态: 只要某个Pi为叶子节点 则当前玩家必胜
. 其实终止态里 一定是: 1个叶子节点 其他都不是叶子节点, 否则如果有多个叶子节点 那游戏早结束了;

G1: a--l          G2: c--l
     --b--l
     
(b,c)局面是必败态(不论怎么操作 对手的局面一定有个叶子节点);
而(a,c)局面是必胜态;

注意: Gi就是个中立组合游戏DAG, 并没有定义胜负态!

LLiGi二级叶子节点, 令当前游戏的局面DAG为G;
根据题意有: G的叶子节点(为必胜态)为: 存在某个Pi为叶子节点;
结论: G的二级叶子节点(为必败态) 一定为: 所有的Pi均为LLi;

#算法#
根据结论, 我们将G的所有必胜态叶子节点删除掉 (即原来的所有二级叶子节点 变成了新图G'的叶子节点), 此时G'的叶子 都是必胜态; 同时把Gi的所有叶子节点也都给删除掉 变成了Gi';
原先问题转换为: 给定若干个中立组合游戏DAGGi', G2',... 令其起点为P1,P2,..., 定义游戏:
初始局面为(P1, P2, ...), 操作为: 选择一个Pi将其移动到后驱节点, 终止态: 当所有Pi为叶子节点 则当前玩家必胜 (这里的定义变了);
. 显然, 这个新G'游戏 是SG游戏;

例题

(非标准型游戏, 转换为: SG游戏) + (拆分Nim): LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=131472839;

错误

假如修改下条件: 终止态: 只要某个Pi为叶子节点 则当前玩家必胜, 改为: > 1 >1 >1个叶子节点时 当前玩家必胜;
那么, 此时 上面的算法 (删除所有叶子节点 就可以转换为标准型) 就失效了!
因为: 此时G的叶子节点 有 > 1 >1 >1Pi为叶子节点, 那么G的二级叶子节点 就不再是: 所有的Pi均为LLi了 (而是: 有1个Pi为叶子 其他的Pi都是LLi);
. 因此你把所有Gi的叶子节点给删除掉 是错误的, 因为G的二级叶子节点 会用到Gi的叶子节点;

例题

(非标准型游戏, 转换为: SG游戏) + (拆分Nim): LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=131472839;

SG游戏/SG函数

定义

对于一个标准型的 中立的 组合游戏DAG, 其上可定义一个SG函数 SG( 节点) = 非负整数(即该图上的每个节点 都有一个SG值), 具体算法为:
1: 对于叶子节点, 其SG值为 0 0 0;
2: 对于非叶子节点X, 令其所有后驱节点的SG值集合为S, 则SG( X) = MEX( S); (MEX操作定义为: S集合里 最小的 未出现的非负整数);

int Get_SG( 局面cur){
	if( `cur为终止局面`){ // 前提是: 所有终止局面 一定是*必败态*;
		return 0;
	}
	
	unordered_set< int> Mex; // 也可以开bool数组记录
	for( `cur的所有后驱nex`){
		Mex.insert( Get_SG( nex));
	}
	int ANS;
	for( ANS = 0; ; ++ANS){
        if( Mex.find( ANS) == mex.end()){
            break;
        }
    }
    return ANS;
}

性质

注意, 只有标准型(即所有终止局面(即叶子节点) 都是必败态的, 而且是中立的 组合游戏上, 才可以定义SG函数;
. 假如不是标准型, 或者不是中立的, 就不可以在其DAG上定义SG函数;

@DELI;

#任意节点X为必败态    ⟺    \iff SG(X)为0# (等价于: 必胜态的节点 其SG值一定是 > 0 >0 >0的)

证明:
DAG的拓扑序一定形如[..., 四级, 三级, 二级, 一级叶子节点]逆序方式遍历:
1: 一级叶子 (全是必败态 即SG值全为 0 0 0); 成立;
2: 二级叶子 (全是必胜态 即SG值全为 > 0 >0 >0); 成立;
3: 三级叶子 (如果存在后驱为必败(其SG值为 0 0 0) 则为必胜态(SG值 > 0 ) >0) >0); 否则后驱均为必胜态(SG值为 > 0 >0 >0) 则为必败态(SG值为 0 0 0)); 成立;
4: 以此类推;

错误

如果你要求解一个标准型的 中立的组合游戏G, 那么你根本无需 对其DAG图 去构造SG函数, 然后通过判断 初始局面的SG值 来判断游戏的输赢;
. 显然就多此一举了, 你明明可以简单的 通过上面组合游戏的算法 (即遍历整个DAG, 求各个节点的胜负态); 否则, 求SG值 还得再乘以个常数;

SG函数的用途, 即一个 即SG游戏;

@DEPRECATED

定义

A,B轮流操作 (操作规则相同), 谁遇到必输状态谁输;

性质

任何局面必然属于一种确定的状态

状态分为两种: 必输和必赢;
任何一个局面, 要么是必输, 要是必赢;
因此, 谁会赢 完全取决于 起始局面, 换句话说, 谁会赢 取决于 谁先手; (假如起始局面是必输的, 那么谁先手谁必输; 输赢与 A , B A,B A,B两人无关, 仅仅取决于 谁先手);

@Delimiter

终结状态, 必然是必输的

所谓终结状态, 也就是游戏停止终结的条件;
任何一个局面, 游戏进行下去, 必然会遇到必输状态, 不会永远进行下去;

算法模板

从必输状态倒推

我们令 l o s s loss loss为必输状态, w i n win win为必赢状态;
我们从终结状态 l o s s loss loss开始, 然后倒推 w i n win win, 最终得到一个序列 l o s s 1 (终结) → w i n 1 → l o s s 2 → w i n 2... loss1 \text{(终结)} \to win1 \to loss2 \to win2 ... loss1(终结)win1loss2win2...;

当我们根据 l o s s loss loss 去反推 w i n win win时, 要保证:
. 对于 w i n win win局面, 一定存在 ∃ \exists 一种操作, 使得 w i n win win局面可以变成 l o s s loss loss局面;

当我们根据 w i n win win 去反推 l o s s loss loss时, 要保证:
. 对于 l o s s loss loss局面, 对于所有 ∀ \forall 操作 (不管怎么操作), l o s s loss loss局面都必然变成 w i n win win局面;

例题

CSDN-129662569

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值