一个点 $(x, y, z)$ 的权值为 $K^{x+y+z}$ ,这个 $K$ 足够大,可以看做正无穷,也就是说求这张图的最大权独立集要尽量把 $x+y+z$ 最大的选满。
那么一个暴力的 $O(n^3)$ 贪心做法就呼之欲出了:从大到小枚举 $S = x + y + z$ ,然后对于所有和为 $S$ 的 $(x, y, z)$ ,如果之前没有与之连边的点被选,那么这个点一定要出现在最优解中。
可以给边定向,边从小连到大,那么整个图就是 DAG ,在 DAG 中抽象这个贪心:每次选择一个未确定的点满足其出边连接到的点都已经确定不在最优解(或者没有出边),然后把该点加入最优解集合,把该点入边连接的点确定为不在最优解。
那么一个点在最优解当且仅当其出边连接到的点都不在最优解,一个点不在最优解当且仅当其出边连接到的点存在一个在最优解的点(或没有出度)。
可以发现这个关系完全可以对应到博弈中的必胜必败态,这个 DAG 可以看做博弈转移图,答案就是所有必败态的权值和。
考虑对于一个 $(x, y, z)$ ,如何快速求出其必胜必败态。由于每次移动只能移动一维,所以这个博弈三维独立,就相当于每个维度有一个子游戏。对于每一维的 DAG 分别求出 SG 值 $f(x), g(y), h(z)$ ,三者异或和就是 $(x, y, z)$ 的 SG 值。
至此,问题转换为求 $\sum_{f(x) \oplus g(y) \oplus h(z) = 0} K^{x+y+z}$ ,其中 $\oplus$ 是异或。
可以用 FWT 可以直接求出 $f, g, h$ 的异或卷积。
但是也有更简单的方法,对于一个博弈转移图,任意一个点的 SG 值上界是 $O(\sqrt{m})$ 的。所以可以 $O(m\sqrt{m})$ 或 $O(m)$ 直接枚举 $f(x), g(y), h(z)$ 统计答案。
这个很重要,因为遭受降智打击后 FWT 容易打挂
参考实现:
#include <cstdio>
#include <algorithm>
#include <vector>
#include <set>
#define debug(...) fprintf(stderr, __VA_ARGS__)
typedef long long ll;
struct {
inline operator int () { int x; return scanf("%d", &x), x; }
} read;
const int maxn = 200005, mod = 998244353;
std::vector<int> G[maxn];
int sg[3][maxn];
ll f[3][maxn];
ll po[maxn * 3];
int main () {
int n = read, len = 1;
while (len < n)
len <<= 1;
po[0] = 1;
ll bs = 1000000000000000000ll % mod;
for (int i = 1; i <= n * 3; i ++)
po[i] = po[i - 1] * bs % mod;
for (int o = 0; o < 3; o ++) {
int m = read;
for (int i = 1; i <= n; i ++)
G[i].clear();
for (int i = 1; i <= m; i ++) {
int u = read, v = read;
if (u < v) G[u].push_back(v);
if (v < u) G[v].push_back(u);
}
for (int i = n; i; i --) {
std::set<int> mex;
for (int j : G[i])
mex.insert(sg[o][j]);
while (mex.count(sg[o][i]))
++ sg[o][i];
}
for (int i = 1; i <= n; i ++)
(f[o][sg[o][i]] += po[i]) %= mod;
}
ll ans = 0;
for (int i = 0; i < 500; i ++)
for (int j = 0; j < 500; j ++)
(ans += f[0][i] * f[1][j] % mod * f[2][i ^ j]) %= mod;
printf("%lld\n", ans);
}
小彩蛋: