公平组合游戏

不是写给自己看的。

ICG

考虑一类两个玩家的零和博弈游戏:

  1. 游戏有一个局面,双方轮流操作,每次操作转移到一个新局面。
  2. 能进行的操作与当前局面相关,与轮到哪一方操作无关(公平性)
  3. 无法操作的一方失败。
  4. 局面之间的转移无环。

这可以看做以局面为节点,转移边的有向无环图上沿边交替移动一个棋子。也就是 ICG 等价于其对应的 有向图游戏。

结论:任一 ICG 要么先手 存在必胜策略(简称先手必胜),要么后手 存在必胜策略(简称先手必败),两者恰有其一。

证明:

考虑归纳定义一个局面是先手必败的(P-position)当且仅当两者至少满足一个:

  1. 这个局面是终止局面(无法进一步操作 / 出度为 \(0\))
  2. 这个局面不能到达任何必败局面。

定义一个局面是先手必胜的(N-position)当且仅当:

  1. 这个局面可以到达至少一个必败局面。

相当于证明任何局面要么是 P-position,要么是 N-position。因为这个图是 DAG,可以从每个出度为 \(0\) 的点出发归纳证明。

一般有向图游戏

对于一般的有向图游戏,我们可以直接拓扑排序求出一个点是必胜还是必败局面,还不需要引入 SG 函数。题外话:这种思想也可以拿来求有环的类公平组合游戏,并判断平局。

Nim 游戏

局面:\(n\) 堆石子,数目分别为 \(a_1,a_2,\cdots,a_n\)。

操作:任选一堆石子 \(i\),令 \(a_i\gets x\) 个,必须满足 \(0\le x< a_i\)。

结论:先手必胜当且仅当初始 \(a_1,a_2,\cdots,a_n\) 的异或和不为 \(0\)。否则先手必败。

证明:

根据定义,证明一种判断 position 的性质的方法的正确性,只需证明三个命题:

  1. 这个判断将所有终止状态判为 P-position;
  2. 根据这个判断被判为 N-position 的局面一定可以移动到某个 P-position;
  3. 根据这个判断被判为 P-position 的局面无法移动到某个 P-position。

第一个命题显然,终止状态只有一个,就是全 \(0\),异或仍然是 \(0\)。

第二个命题,即对于某个局面 \(a_1,a_2,\cdots,a_n\),若异或和不为 \(0\),一定存在某个合法的移动,将 \(a_i\) 改变成 \(a_i'\) 后满足异或和为 \(0\)。不妨设 \(a_1,a_2,\cdots,a_n\) 异或和为 \(k\),则一定存在某个 \(a_i\),它的二进制表示在 \(k\) 的最高位上是 \(1\)。这时 \(a_i\oplus k<a_i\) 一定成立,因为前者不满足。则我们可以将ai改变成 \(ai'=ai \oplus k\),此时异或和为 \(0\)。

第三个命题,即即对于某个局面 \(a_1,a_2,\cdots,a_n\),若异或和为 \(0\),一定不存在某个合法的移动,将 \(a_i\) 改变成 \(a_i'\) 后满足异或和为 \(0\),否则 \(a_i=a_i'\)。

用这种特殊的有向图游戏的思想,可以解决下面的问题。

SG 函数

考虑 有向图游戏的和:有多个 完全独立 的有向图游戏,它们共同构成一个新的游戏。这个游戏中:

  1. 局面即为所有有向图游戏的局面共同构成的状态。
  2. 操作为选择恰好一个可操作的有向图游戏做恰好一次操作。

因为局面变为了指数级,所以直接跑不再可行,必须借助相互独立的性质计算。


设有向图游戏 \(G\) 的一个局面 \(p\),定义

\(SG(p)=0\) 当且仅当 \(p\) 是一个必败局面。

\(SG(p)=x>0\) 当且仅当 \(\forall 0\le y<x\),\(p\) 可以转移到某个 \(SG(q)=y\) 的局面 \(q\);并且 不能转移到 \(SG(q)=x\) 的任何局面 \(q\)。

发现这个东西其实和 Nim 游戏没有本质区别,只是把 \(a_i\) 换成了 \(SG(p)\)。但是有一个问题:没有保证 \(x>0\) 时不能转移到 \(SG(q)>x\) 的局面 \(q\)。但可以证明这是没有必要的,因为根据定义,若这一步转移到 \([0,x)\) 均不可获胜,下一步对方仍然可以转移回 \(SG(p')=x\)。注意 \(p'\ne p\),并非形成了环只是 \(SG\) 值不变。

于是得到结论:

  1. 若 \(s\) 为有向图游戏 \(G\) 的起点,定义 \(SG(G)=SG(s)\)
  2. 游戏 \(\sum G\) 先手必胜,当且仅当所有 \(SG(G)\) 的异或和不为 \(0\);否则先手必败。

Depot

AGC016F Games on DAG

给定一个有向无环图 \(G=(V,E)\),定义 原游戏 为 从 \(1,2\) 两个节点出发形成的两个有向图游戏的和。求满足 \(E'\subseteq E\) 的所有 \(2^{|E|}\) 个生成子图 \(G'=(V,E')\) 中,使得原游戏先手必胜的数目。\(n\le15\)

等价于 \(sg_1\neq sg_2\)。这个难以求解,考虑改为 \(2^{|E|}\) 减去使得 \(sg_1=sg_2\) 的子图数目。


首先考虑求解 \(sg_1=0\) 的方案数。根据数据范围,设 \(f_P\) 表示 \(SG\) 值为 \(0\) 的集合为 \(P\subseteq V\) 情况下的方案数。另外

\[\forall 1\notin P, f_P=0 \]

这个东西不用 DP,可以 \(O(2^nm)\) 直接算,只需考虑四类边在子图的存在性:

首先考虑 \(P\) 内部,由定义这些边均不能存在。然后考虑从 \(P\) 连向 \(N\) 的边,由定义,这些边可以随便连。接下来是从 \(N\) 连向 \(P\) 的边,由定义每个 \(p\in N\) 至少应有一条这样的边存在。然后对于 \(N\) 内部 ,这些点之间可以随便连,因为只需要 \(SG\) 非 \(0\) 就符合状态。直接乘法原理求出四类边的添加方案数。最后总方案数即为 \(\sum f\)。

注意这时候我们考虑的 \(N\) 集合只限定了 \(\forall p\in N, SG(p) \ge 1\) 的条件,并没有考虑具体 \(SG\) 值。


考虑如何保证 \(sg_1=sg_2k\),这时候有人类智慧:

将 \(SG(p)=0,1,2,\cdots\) 依次加入。具体来说:考虑除去所有 \(P\) 中节点,得到导出子图 \(G[N]\),容易证明其中每个节点 相比原来此节点 \(SG\) 值恰好减少 \(1\)。所以我们可以做 \(O(n)\) 轮 DP 解决。

设 \(g_S\) 表示只考虑点集 \(S\in V\),要求 \(sg_1=sg_2\) 的方案数。其中

\[\forall [1\in S]\neq[2\in S], g_S=0 \]

注意到这个条件使得 \(sg_1=sg_2\) 相等(因为两点在同一轮被加入)。

对于 \(g_S\),每次枚举子集 \(N\),从 \(g_N\) 转移。然后按照前述算法统计\(G[P],cross(P,N),cross(N,P)\) 的边,对于 \(G[N]\) 中的边,它就相当于只考虑这个导出子图G[N]的满足条件的方案数,即子问题 \(g_N\)。

最后答案即为 \(g_V\)。复杂度 \(O(3^nn)\)。

#include <bits/stdc++.h>
#define popc __builtin_popcount
#define ctz __builtin_ctz

typedef long long ll;
const int maxn = 18, maxj = 1 << maxn, mod = 1000000007;

int n, m, all;
int e[maxn], f[maxj];

int main() {
	int i, j, u, v, k, real, N, P, sum = 1;
	scanf("%d%d", &n, &m), all = (1 << n) - 1;
	for (i = 0; i < m; ++i)
		scanf("%d%d", &u, &v), e[--u] |= 1 << --v, sum = sum * 2 % mod;
	for (f[0] = 1, i = 2; i <= all; i += 2) // 保证 1 不在其中
		// 枚举子集
		for (j = i; ; j = (j - 1) & i) {
			ll res = 1;
			real = i + (i >> 1 & 1); // 若 2 在其中,加入 1
			N = j + (j >> 1 & 1), P = real & ~N;
			// 枚举其中节点,转移。
			for (k = real; k; k &= k - 1)
				v = __builtin_ctz(k), res = res * (N >> v & 1 ? (1 << __builtin_popcount(e[v] & P)) - 1 : 1 << __builtin_popcount(e[v] & N)) % mod;
			f[i] = (f[i] + res * f[j]) % mod;
			if (!j) break;
		}
	sum -= f[all - 1], printf("%d\n", sum + (sum >> 31 & mod));
	return 0;
}
posted @ 2023-08-24 21:26  音街ウナ  阅读(82)  评论(1)    收藏  举报