1 条题解
-
0
E1. 游戏(简单版) 详细题解
题目理解
有一棵以 为根的树,每个节点 有权值 。两人轮流操作,Cirno 先手。
每轮操作:假设上一轮对手选了节点 (第一轮无限制),当前玩家必须选一个未被删除的节点 ,满足 ,然后删除节点 的整个子树(包括 自己)。
无法操作的玩家获胜。双方都采取最优策略。
要求:如果 Cirno 有必胜策略,输出她第一回合可能选择的任意一个节点;否则输出 。关键结论
Cirno 第一步选节点 能够获胜 当且仅当 在树中(删除 的子树之前)存在至少一个节点 ,使得:
- 不在 的子树内;
- 。
换句话说, 的子树之外的最大权值必须严格大于 。
为什么这个条件正确?
-
如果子树外不存在更大权值的节点
那么 Cirno 选 之后,剩下的所有节点权值都 。由于下一轮 Daiyousei 必须选一个权值 的节点,她无法操作。根据规则,“第一个无法操作的玩家获胜”,所以 Daiyousei 获胜,Cirno 输。
因此这样的 不可取。 -
如果子树外存在更大权值的节点
设 为所有节点权值的最大值。考虑从大到小归纳:- 权值最大的节点(等于 )必败:因为选了它之后对手无法操作,对手获胜。
- 对于权值为 的节点,如果它的子树外存在权值为 的节点,那么对手只能选那个 (因为只有 的节点可选),而选 是败招,所以对手输,当前玩家赢。如果不存在,则对手无步可走,对手赢,当前玩家输。
- 依此类推,一个节点 是必胜态,当且仅当存在一个不在其子树内且权值更大的节点是必败态。
可以证明,这个条件等价于“子树外存在任意一个权值更大的节点”。因为从全局最大权值开始,所有“子树外更大”的节点会通过归纳传递必胜态。
因此,只要 的子树外有任何一个权值大于 的节点,Cirno 第一步选 就必胜。
算法实现
- 对树做一次 DFS,求出每个节点的:
- :DFS 序(进入时间戳, 到 )
- :以 为根的子树中最大的 DFS 序(即子树区间 )
- 构造一个数组 ,表示 DFS 序为 的节点编号。
- 预处理前缀最大值 和后缀最大值 。
- 对于每个节点 :
- 它的子树之外对应的 DFS 序区间为 和 。
- 子树外的最大权值 = (边界情况视为 )。
- 如果这个值 ,则 是一个可行的第一步选择。
- 输出任意一个满足条件的节点(代码中输出权值最大的那个),如果没有则输出 。
时间复杂度 ,空间复杂度 。
代码实现(C++)
#include "bits/stdc++.h" using namespace std; const int N = 1e6 + 5; vector<int> e[N]; int w[N], dfn[N], nfd[N], pre[N], suf[N], low[N]; bool ed[N]; int id; void dfs(int u) { if (ed[u]) return; ed[u] = 1; dfn[u] = ++id; nfd[id] = u; for (int v : e[u]) dfs(v); ed[u] = 0; low[u] = id; } int main() { ios::sync_with_stdio(0); cin.tie(0); int T; cin >> T; while (T--) { int n; cin >> n; for (int i = 1; i <= n; i++) { e[i].clear(); id = 0; } for (int i = 1; i <= n; i++) cin >> w[i]; for (int i = 1; i < n; i++) { int u, v; cin >> u >> v; e[u].push_back(v); e[v].push_back(u); } dfs(1); for (int i = 1; i <= n; i++) pre[i] = max(pre[i-1], w[nfd[i]]); suf[n+1] = 0; for (int i = n; i >= 1; i--) suf[i] = max(suf[i+1], w[nfd[i]]); int mx = 0; for (int i = 1; i <= n; i++) { int outside = max(pre[dfn[i]-1], suf[low[i]+1]); if (outside > w[i] && w[i] > w[mx]) mx = i; } cout << mx << '\n'; } return 0; }
- 1
信息
- ID
- 7216
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者