1 条题解
-
0
E2. 游戏(困难版)详细题解
问题重述
给定一棵以 为根的有根树,每个节点 有一个权值 。两人轮流操作,Cirno 先手。每轮操作:假设上一轮对手选了节点 (第一轮无限制),当前玩家必须选择一个未被删除的节点 ,满足 ,然后删除节点 的整棵子树(包括 本身)。无法操作的玩家获胜。双方都采取最优策略。对于每个测试用例,输出 Cirno 在第一回合可以选择的所有节点(按升序);若不存在则输出 。
博弈分析
设 。
- 若当前玩家选择了一个权值为 的节点,则对手无法操作,对手获胜 → 选权值为 的节点是必败的。
- 考虑权值为 的节点 。
如果 的子树外存在权值为 的节点 ,那么选 后对手只能选 ,而选 是必败态,因此 是必胜态。
如果 的子树内包含了所有权值为 的节点,则选 后所有 节点被删除,对手无合法操作,对手获胜 → 是必败态。 - 依此类推,对于任意节点 ,定义 。
若 中存在至少一个节点不在 的子树内,则选 后对手必须从 中选择一个节点,而所有 中的节点(因为权值更大)都是必败态,因此对手必败 → 是必胜态。
若 中所有节点都在 的子树内,则选 后 被全部删除,对手无步可走,对手获胜 → 是必败态。
核心结论:节点 是 Cirno 第一步必胜选择 当且仅当 在 的子树外存在至少一个节点 满足 。
转化为树上区间查询
对树进行 DFS,得到每个节点的 DFS 序 和其子树内最大的 DFS 序 。节点 的子树对应连续区间 。子树外的节点对应两个区间: 和 。
定义数组 表示 DFS 序为 的节点编号。预处理:
- 前缀最大值 $pre[x] = \max\{w_{nfd[1]}, w_{nfd[2]}, \dots, w_{nfd[x]}\}$,并令 。
- 后缀最大值 $suf[x] = \max\{w_{nfd[x]}, w_{nfd[x+1]}, \dots, w_{nfd[n]}\}$,并令 。
对于节点 ,子树外的最大权值为:
$$out = \max\big(pre[dfn[i]-1],\; suf[low[i]+1]\big) $$判断条件为:。
算法步骤
- 读入 组测试数据。
- 对每组数据:
a. 读入 和权值 。
b. 建图(无向边)。
c. 从根 开始 DFS,记录 、 和 数组。
d. 计算前缀最大值数组 。
e. 计算后缀最大值数组 。
f. 遍历所有节点 ():
计算 。
如果 ,则将 加入答案集合。
g. 将答案集合排序后输出:先输出个数 ,再按升序输出所有节点。如果答案集合为空,输出 。
复杂度分析
- DFS 遍历:。
- 预处理前缀/后缀:。
- 遍历判断:。
- 排序答案:,其中 为答案个数,。
- 总复杂度:,实际 总和不超过 ,完全可行。
- 空间复杂度:。
代码实现(C++)
#include <bits/stdc++.h> using namespace std; const int N = 4e5 + 10; vector<int> e[N]; int w[N], dfn[N], nfd[N], low[N], pre[N], suf[N]; int n, id; void dfs(int u, int fa) { dfn[u] = ++id; nfd[id] = u; for (int v : e[u]) { if (v == fa) continue; dfs(v, u); } low[u] = id; } void solve() { cin >> n; for (int i = 1; i <= n; ++i) { cin >> w[i]; e[i].clear(); } for (int i = 1; i < n; ++i) { int u, v; cin >> u >> v; e[u].push_back(v); e[v].push_back(u); } id = 0; dfs(1, 0); pre[0] = 0; 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]]); } vector<int> ans; for (int i = 1; i <= n; ++i) { int outside = max(pre[dfn[i]-1], suf[low[i]+1]); if (outside > w[i]) { ans.push_back(i); } } sort(ans.begin(), ans.end()); if (ans.empty()) { cout << "0\n"; } else { cout << ans.size(); for (int x : ans) cout << ' ' << x; cout << '\n'; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T; cin >> T; while (T--) solve(); return 0; }
- 1
信息
- ID
- 7222
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者