Game of CS 题解

前言

题目链接:洛谷UVA

题意简述

Jolly 和 Emily 在玩一个游戏。游戏在一棵编号为 \([0, n-1]\) 的有根树上进行,根节点是 \(0\),每条边都有一个长度,初始所有边都没有颜色。

玩家只能经过没有被完全染色的边。在一次操作中,从根节点出发,在所能到达的边集中选择一条边,并将其染上 \(1\) 单位长度的颜色。不能进行染色的玩家则输掉这个游戏。

Emily 先手,假设他们都采取最优策略,请问谁会赢?

题目分析

公平游戏,考虑求 SG 函数,设 \(\operatorname{SG}(yzh)\) 表示以 \(yzh\) 为根的子树的 SG 函数。答案就是求 \(\operatorname{SG}(root)\)

对于叶子节点 \(yzh\),后继状态是空集,有 \(\operatorname{SG}(yzh) = 0\)

考虑非叶子节点 \(yzh\),对于她的每一个孩子 \(xym\)\(yzh\)\(yzh\)\(xym\) 的边和以 \(xym\) 为根的子树都能构成一棵树,求解这些树的 SG 函数是互不相关的子问题,所以 \(\operatorname{SG}(yzh)\) 便是这些子问题的 SG 函数的 Nim 和。

那么只需考虑往 \(xym\) 这一棵子树上加上其与 \(yzh\) 之间的边对 SG 函数的影响。设这条边的长度为 \(l\)

\(l = 1\) 时,我们有子问题的 SG 函数值为 \(\operatorname{SG}(xym) + 1\)。考虑以下证明。

证明:

首先发现对于 \(\operatorname{SG}(xym) = k\) 等价于 \(xym\) 下面接了 \(k\) 条边权为 \(1\) 的边。因为类似 Nim 游戏,如果后手把 \(\operatorname{SG}(xym)\) 增加,那么先手一定有办法把 \(\operatorname{SG}(xym)\) 变回原来的值。所以操作后 \(\operatorname{SG}(xym)\) 只能减少,相当于在 \(k\) 条边中选一个位置截断。

所以在子问题基础上增加一条边,其 SG 函数值也会增加 \(1\)

故结论成立。

接下来考虑 \(l > 1\) 的情况。经过手玩,容易发现规律。这里先给出结论:子问题 SG 函数值为 \(\operatorname{SG}(xym) \operatorname{xor} \left (l \bmod 2 \right )\)。以下是证明:

证明:

\(l = 2\) 时,归纳 \(\operatorname{SG}(xym)\),结论显然成立。

接下来假设 \(l = k - 1\) 时结论成立,其中 \(k \bmod 2 = 1\),下证 \(l = k\) 时结论成立。

对于 \(\operatorname{SG}(xym) = 0\) 时,显然成立。假设对于 \(\operatorname{SG}(xym) \leq p - 1\) 均成立,下证对于 \(\operatorname{SG}(xym) = p\) 成立。

先手若给这条边染色,则根据假设,后继局面的 SG 函数为 \(\operatorname{SG}(xym) \operatorname{xor} \left ((k - 1) \bmod 2 \right ) = \operatorname{SG}(xym)\)

先手若改变 \(\operatorname{SG}(xym)\)\(\operatorname{SG}(xym)' \in [0, \operatorname{SG}(xym) - 1]\)。根据假设,子问题的 SG 函数为 \(\operatorname{SG}(xym)' \operatorname{xor} 1\)。若 \(\operatorname{SG}(xym) \bmod 2=0\)\(\operatorname{SG}(xym)' \operatorname{xor} 1 \in [0, \operatorname{SG}(xym) - 1]\);反之,\(\operatorname{SG}(xym)' \operatorname{xor} 1 \in [0, \operatorname{SG}(xym) - 2] \cup \lbrace \operatorname{SG}(xym) \rbrace\)

综合两种情况,若 \(\operatorname{SG}(xym) \bmod 2=0\),子问题 SG 函数值为 \(\operatorname{mex} \lbrace \operatorname{SG}(xym) \rbrace \cup [0, \operatorname{SG}(xym) - 1] = \operatorname{mex} [0, \operatorname{SG}(xym)] = \operatorname{SG}(xym) + 1\);反之,子问题 SG 函数值为 \(\operatorname{mex} \lbrace \operatorname{SG}(xym) \rbrace \cup [0, \operatorname{SG}(xym) - 2] \cup \lbrace \operatorname{SG}(xym) \rbrace = \operatorname{mex} [0, \operatorname{SG}(xym) - 2] = \operatorname{SG}(xym) - 1\)

发现子问题的 SG 函数值恰好是 \(\operatorname{SG}(xym) \operatorname{xor} 1\)。故此时结论成立。

类似地,接下来假设 \(l = k - 1\) 时结论成立,其中 \(k \bmod 2 = 0\),下证 \(l = k\) 时结论成立。

对于 \(\operatorname{SG}(xym) = 0\) 时,显然成立。假设对于 \(\operatorname{SG}(xym) \leq p - 1\) 均成立,下证对于 \(\operatorname{SG}(xym) = p\) 成立。

先手若给这条边染色,则根据假设,后继局面的 SG 函数为 \(\operatorname{SG}(xym) \operatorname{xor} \left ((k - 1) \bmod 2 \right ) = \operatorname{SG}(xym) \operatorname{xor} 1\)

先手若改变 \(\operatorname{SG}(xym)\)\(\operatorname{SG}(xym)' \in [0, \operatorname{SG}(xym) - 1]\)。根据假设,子问题的 SG 函数为 \(\operatorname{SG}(xym)' \operatorname{xor} 0 = \operatorname{SG}(xym)'\)

综合两种情况,无论 \(\operatorname{SG}(xym)\),子问题的 SG 函数值均为 \(\operatorname{SG}(xym)\),也即 \(\operatorname{SG}(xym) \operatorname{xor} 0\)。故结论成立。

综上所述,子问题 SG 函数值为 \(\operatorname{SG}(xym) \operatorname{xor} \left (l \bmod 2 \right )\) 对于所有 \(l \geq 2\) 均成立。证毕。

有了这般证明,代码使用一次深搜维护每个点的 SG 函数就行了。

代码实现

在洛谷上目前只有我一个人过,所以妥妥的 Rank1。

// #pragma GCC optimize(3)
// #pragma GCC optimize("Ofast", "inline", "-ffast-math")
// #pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <iostream>
#include <cstdio>
#define debug(a) cerr << "Line: " << __LINE__ << " " << #a << endl
#define print(a) cerr << #a << "=" << (a) << endl
#define file(a) freopen(#a".in", "r", stdin), freopen(#a".out", "w", stdout)
#define main Main(); signed main(){ return ios::sync_with_stdio(0), cin.tie(0), Main(); } signed Main
using namespace std;

#include <cstring>
#include <vector>

int cas, n, sg[1010];
vector<pair<int, int> > edge[1010];

void dfs(int now, int fa){
	for (const auto & [to, len]: edge[now]) if (to != fa){
		dfs(to, now);
		if (len == 1) sg[now] ^= sg[to] + 1;
		else sg[now] ^= sg[to] ^ (len & 1);
	}
}

void solve() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) edge[i].clear(), sg[i] = 0;
	for (int i = 1, u, v, w; i <= n - 1; ++i){
		scanf("%d%d%d", &u, &v, &w), ++u, ++v;
		edge[u].push_back(make_pair(v, w));
		edge[v].push_back(make_pair(u, w));
	}
	dfs(1, 1314520736);
    printf("Case %d: ", ++cas);
	puts(sg[1] ? "Emily" : "Jolly");
}

signed main() {
    int t; scanf("%d", &t);
	while (t--) solve();
    return 0;
}
posted @ 2024-06-29 09:18  XuYueming  阅读(9)  评论(0编辑  收藏  举报