公平组合游戏
不是写给自己看的。
ICG
考虑一类两个玩家的零和博弈游戏:
- 游戏有一个局面,双方轮流操作,每次操作转移到一个新局面。
- 能进行的操作与当前局面相关,与轮到哪一方操作无关(公平性)
- 无法操作的一方失败。
- 局面之间的转移无环。
这可以看做以局面为节点,转移边的有向无环图上沿边交替移动一个棋子。也就是 ICG 等价于其对应的 有向图游戏。
结论:任一 ICG 要么先手 存在必胜策略(简称先手必胜),要么后手 存在必胜策略(简称先手必败),两者恰有其一。
证明:
考虑归纳定义一个局面是先手必败的(P-position)当且仅当两者至少满足一个:
- 这个局面是终止局面(无法进一步操作 / 出度为 \(0\))
- 这个局面不能到达任何必败局面。
定义一个局面是先手必胜的(N-position)当且仅当:
- 这个局面可以到达至少一个必败局面。
相当于证明任何局面要么是 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 的性质的方法的正确性,只需证明三个命题:
- 这个判断将所有终止状态判为 P-position;
- 根据这个判断被判为 N-position 的局面一定可以移动到某个 P-position;
- 根据这个判断被判为 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 函数
考虑 有向图游戏的和:有多个 完全独立 的有向图游戏,它们共同构成一个新的游戏。这个游戏中:
- 局面即为所有有向图游戏的局面共同构成的状态。
- 操作为选择恰好一个可操作的有向图游戏做恰好一次操作。
因为局面变为了指数级,所以直接跑不再可行,必须借助相互独立的性质计算。
设有向图游戏 \(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\) 值不变。
于是得到结论:
- 若 \(s\) 为有向图游戏 \(G\) 的起点,定义 \(SG(G)=SG(s)\)
- 游戏 \(\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\) 情况下的方案数。另外
这个东西不用 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\) 的方案数。其中
注意到这个条件使得 \(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;
}

浙公网安备 33010602011771号